Vill du vara del av diskussionerna i forumet, ställa frågor eller hjälpa andra? Registrera dig här!

.NET 2.2 Api key authentication genom middleware. Hur jämföra nycklar?

Trädvy Permalänk
Medlem
Registrerad
Okt 2012

.NET 2.2 Api key authentication genom middleware. Hur jämföra nycklar?

Tja.
jag jobbar på ett projekt som inkluderar att skapa en API med autentisering genom användning av API nycklar. Jag har skapat en middleware som ska hantera API-requesten och som checkar om den angivna API-nyckeln är giltig. Seda kommer jag att skapa en service med en "validator" som ska begräna användaren från att ha åtkomst till data som inte tillhör den angivna API nyckeln i fråga. Det finns alltså ett "CompanyId" lagrat ihop med "APIKey" i tabellen för CompanyData.

Jag är färsk på .NET 2.2 så ha gärna tålamod med mitt idioti Denna kodsnutten är från min middleware, där autentiseringen skall ske. I första kodsnutten tänker jag att jag hämtar existerande API nycklar från där de är lagrade i databasen (det vill säga i CompanyData.ApiKey). Snälla, rätta mig om jag tänker fel här för jag är ny till hela syntaxen och uppskattar verkligen om någon skulle kunna knuffa mig i rätt riktning.

namespace API.Middleware{ public class APIKeyMessageHandlerMiddleware { private readonly ApplicationDbContext _db; public async Task<ActionResult<CompanyData>> ApiKeyToCheck(string GetCompanyApiKey) { CompanyData companyApiKey = await _db.Companydatas.SingleOrDefaultAsync(x => x.ApiKey == GetCompanyApiKey); return companyApiKey; } private RequestDelegate next; public APIKeyMessageHandlerMiddleware(RequestDelegate next) { this.next = next; }

Vidare vill jag jämföra de lagrade API nycklarna med vad användaren anger i API headern "APIKEY". Om den angivna nyckeln existerar i databasen skall APIn acceptera förfrågan. Här är det jag har fastnat, jag vet liksom inte hur jag ska definiera det.
--- Fortsättning på kodsnutt #1 --

public async Task Invoke(HttpContext context) { bool validKey = false; var checkApiKeyExists = context.Request.Headers.ContainsKey("APIKey"); if (checkApiKeyExists) { if (context.Request.Headers["APIKey"].Equals(ApiKeyToCheck(Hur får jag de lagrade nycklarna här så att jag kan jämföra dem med vad användaren anger?))) { validKey = true; } } if (!validKey) { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; await context.Response.WriteAsync("permission denied"); } else { await next.Invoke(context); } } } public static class MyHandlerExtensions { public static IApplicationBuilder UseAPIKeyMessageHandlerMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<APIKeyMessageHandlerMiddleware>(); } }

Stort tack!

Trädvy Permalänk
Medlem
Plats
Falun
Registrerad
Dec 2003

@joakimR:

Det ser ut som att du använder dina egna metoder lite knasigt.

Borde väl användas på detta sätt?

if (checkApiKeyExists) { var cd = ApiKeyToCheck(context.Request.Headers["APIKey"]); if (cd != null) { //Profit } }

ηλί, ηλί, λαμά σαβαχθανί!?

Trädvy Permalänk
Medlem
Plats
Örebro
Registrerad
Feb 2011

Hade det varit jag, så skulle jag ha lagrat nycklarna i någon mellanlagring (läs: cache) för att inte hamra ner databasen med allt för många anrop...meeenee, det är ju också något du kan koppla på senare.

För att hämta nycklarna från databasen så skulle du kunna använda något så enkelt som nuget-paketet 'System.Data.SqlClient' (använd inte något som ligger lokalt, du kommer bara skapa fler problem än vad du löser).

Och sedan skriver du typ något med:

using (SqlConnection conn = new SqlConnection(config.GetConnectionString("sql-connectionstring-name-something")) { conn.Open(); try { using (SqlCommand cmd = new SqlCommand("SELECT * FROM CompanyTable") { // Kör commandot och hantera resultatet } } catch (Exception ex) { throw ex; } }

property:n 'config' kommer från IConfiguration (så du kanske behöver kolla på hur Dependency Injection fungerar, om du inte redan har gjort det). Tänk på att det här är bara i stora drag hur du skulle kunna prata med databasen för att få nycklarna, och jag har ingen aning om syntexen är rätt/fungerar ihop med det du har idag (skriver detta på en ipad nu).

Men det borde peka dig åt rätt riktning för att ta dig vidare (=

Trädvy Permalänk
Medlem
Registrerad
Okt 2012
Skrivet av Leedow:

@joakimR:

Det ser ut som att du använder dina egna metoder lite knasigt.

Borde väl användas på detta sätt?

if (checkApiKeyExists) { var cd = ApiKeyToCheck(context.Request.Headers["APIKey"]); if (cd != null) { //Profit } }

Det möjliggör väl fortfarande inte problemet kring att kunna jämföra med de existerande nycklarna i databasen eller hur menar du?

Trädvy Permalänk
Medlem
Registrerad
Okt 2012
Skrivet av asabla:

Hade det varit jag, så skulle jag ha lagrat nycklarna i någon mellanlagring (läs: cache) för att inte hamra ner databasen med allt för många anrop...meeenee, det är ju också något du kan koppla på senare.

För att hämta nycklarna från databasen så skulle du kunna använda något så enkelt som nuget-paketet 'System.Data.SqlClient' (använd inte något som ligger lokalt, du kommer bara skapa fler problem än vad du löser).

Och sedan skriver du typ något med:

using (SqlConnection conn = new SqlConnection(config.GetConnectionString("sql-connectionstring-name-something")) { conn.Open(); try { using (SqlCommand cmd = new SqlCommand("SELECT * FROM CompanyTable") { // Kör commandot och hantera resultatet } } catch (Exception ex) { throw ex; } }

property:n 'config' kommer från IConfiguration (så du kanske behöver kolla på hur Dependency Injection fungerar, om du inte redan har gjort det). Tänk på att det här är bara i stora drag hur du skulle kunna prata med databasen för att få nycklarna, och jag har ingen aning om syntexen är rätt/fungerar ihop med det du har idag (skriver detta på en ipad nu).

Men det borde peka dig åt rätt riktning för att ta dig vidare (=

Förlåt, kanske otydlig. Jag gör detta projektet åt och i samarbete med ett företag och har då inte direktkontakt med deras databas utan jag har endpoints till deras datamodeller. Med andra ord, datamodellen har CompanyData och i den tabellen finns bl.a APIKey och companyID m.m. Jag kanske har fel, men jag kan väl inte köra sqlqueries då?

Trädvy Permalänk
Medlem
Registrerad
Dec 2015
Skrivet av joakimR:

Förlåt, kanske otydlig. Jag gör detta projektet åt och i samarbete med ett företag och har då inte direktkontakt med deras databas utan jag har endpoints till deras datamodeller. Med andra ord, datamodellen har CompanyData och i den tabellen finns bl.a APIKey och companyID m.m. Jag kanske har fel, men jag kan väl inte köra sqlqueries då?

Du verkar köra Entity Framework Core och då finns det inte mycket anledning att köra direkta databasförfrågningar (via System.Data.SQLClient) så vida du inte har en bra anledning. Om du kan göra det eller inte beror nog mest på om du har tillgång till ConnectionString eller inte.

Att ha en cache, som @asabla säger kan vara en god ide, men är som sagt något du kan lägga på efteråt.

Jag är osäker på hur bra API-nycklar (och Basic Authentication) är i framtiden, med tanke på replay-attack-risken med TLS 1.3 och 0-RTT. Någon som har bättre koll får gärna kommentera detta!

Som kuriosa kan det vara kul att titta på Azure API Management som fixar administration och kontroll av API-nycklar åt dig. Det måste dock i ditt fall kombineras med ytterligare authorization i vilket fall som helst. Och det är inte direkt gratis.

Vad jag ser har du i princip redan allt du behöver. Någonstans behöver du dock jämföra din utlästa CompanyData med att requesten faktiskt gäller detta företag och hindra requesten om den gäller fel företag.

Trädvy Permalänk
Medlem
Registrerad
Okt 2012
Skrivet av KAD:

Du verkar köra Entity Framework Core och då finns det inte mycket anledning att köra direkta databasförfrågningar (via System.Data.SQLClient) så vida du inte har en bra anledning. Om du kan göra det eller inte beror nog mest på om du har tillgång till ConnectionString eller inte.

Att ha en cache, som @asabla säger kan vara en god ide, men är som sagt något du kan lägga på efteråt.

Jag är osäker på hur bra API-nycklar (och Basic Authentication) är i framtiden, med tanke på replay-attack-risken med TLS 1.3 och 0-RTT. Någon som har bättre koll får gärna kommentera detta!

Som kuriosa kan det vara kul att titta på Azure API Management som fixar administration och kontroll av API-nycklar åt dig. Det måste dock i ditt fall kombineras med ytterligare authorization i vilket fall som helst. Och det är inte direkt gratis.

Vad jag ser har du i princip redan allt du behöver. Någonstans behöver du dock jämföra din utlästa CompanyData med att requesten faktiskt gäller detta företag och hindra requesten om den gäller fel företag.

Precis, det är EFCore som körs. Jag ska kika på det med cache!
Jag har inte forskat i hur säkert det är med api nycklar, då det är specifikt detta företaget har angett som krav
Det kommer att kombineras med authorization, vilket ska ske i min validator-service. I min validator innebär det att ett företag endast kommer att se datan som tillhör respektive APIKey, vilket görs genom en jämförelse med det companyId som tillhör dess APIKey. Lagring etc av API nycklar är inget som ligger på mitt bord, utan jag ska bara skapa en tredjepartsväg och integration för kunder till dess data genom min API.

Har du nån idé kring hur jag jämför den angivna APINyckeln i headern med existerande nycklar som jag hämtar i metoden i första kodsnutten?

Trädvy Permalänk
Medlem
Registrerad
Jul 2017
Skrivet av KAD:

Du verkar köra Entity Framework Core och då finns det inte mycket anledning att köra direkta databasförfrågningar (via System.Data.SQLClient) så vida du inte har en bra anledning. Om du kan göra det eller inte beror nog mest på om du har tillgång till ConnectionString eller inte.

Att ha en cache, som @asabla säger kan vara en god ide, men är som sagt något du kan lägga på efteråt.

Jag är osäker på hur bra API-nycklar (och Basic Authentication) är i framtiden, med tanke på replay-attack-risken med TLS 1.3 och 0-RTT. Någon som har bättre koll får gärna kommentera detta!

Som kuriosa kan det vara kul att titta på Azure API Management som fixar administration och kontroll av API-nycklar åt dig. Det måste dock i ditt fall kombineras med ytterligare authorization i vilket fall som helst. Och det är inte direkt gratis.

Vad jag ser har du i princip redan allt du behöver. Någonstans behöver du dock jämföra din utlästa CompanyData med att requesten faktiskt gäller detta företag och hindra requesten om den gäller fel företag.

BA används fortfarande av t.ex. Stripe som har väldigt höga processkrav på sig om vad som anses vara OK eller inte att använda.

Tror inte man behöver oroa sig över det i dagsläget även om man kan ha det i åtanke.

Ska man vara riktigt noggrann bör man köra något som t.ex. Vault och vid t.ex. databas förfrågningar efterfrågar nya credentials till databasen vid varje uppstart / en gång per dygn / vart åttonde timme.

OT:

// Kolla Company ID i requesten till dig
// Hämta credentials från servern
// Jämför credentials från servern med den skickad till dig via requesten

Trädvy Permalänk
Medlem
Registrerad
Dec 2015
Skrivet av joakimR:

Har du nån idé kring hur jag jämför den angivna APINyckeln i headern med existerande nycklar som jag hämtar i metoden i första kodsnutten?

Ähm, det behöver du inte, du har ju redan gjort jämförelsen när du hämtar CompanyData. Du har läst upp CompanyData som motsvarar API-nyckeln i headern.

Visa i stället signaturen (och route) för en Controller-metod. Finns ditt företags-id med där?

I så fall, ladda datan för det som ska hämtas, använd Include() för att få med all data upp till företag-tabellen. Är det samma företag som avses i jämförelse med CompanyData, i så fall är requesten OK.

Utmaningen blir att generalisera det hela. Jag har ingen bra ide för det.

Trädvy Permalänk
Medlem
Registrerad
Okt 2012
Skrivet av KAD:

Ähm, det behöver du inte, du har ju redan gjort jämförelsen när du hämtar CompanyData. Du har läst upp CompanyData som motsvarar API-nyckeln i headern.

Jag kanske tänker fel kring det hela. Men jag resonerar såhär:

Här hämtar jag existerande nycklar som finns lagrade i CompanyData.ApiKey. Det returnas sedan till GetCompanyApiKey.

public async Task<ActionResult<CompanyData>> ApiKeyToCheck(string GetCompanyApiKey) { CompanyData companyApiKey = await _db.Companydatas.SingleOrDefaultAsync(x => x.ApiKey == GetCompanyApiKey); return companyApiKey; }

Nedan är det jämförelsen sker kring vad användaren anger i header APIKey i APIn. Det vill säga, här behöver jag jämföra med de nycklar som finns lagrade och som jag hämtade. Hur får jag in det i Equals() ? Hur definierar jag att det är värdet som returneras i GetCompanyApiKey som den ska jämföra med? Jag kan ju liksom inte skriva Equals(ApiKeyToCheck(GetCompanyApiKey)).

if (context.Request.Headers["APIKey"].Equals(ApiKeyToCheck(Hur får jag de lagrade nycklarna här så att jag kan jämföra dem med vad användaren anger?))) { validKey = true; }

Trädvy Permalänk
Medlem
Plats
Falun
Registrerad
Dec 2003

@joakimR:

Som det ser ut just nu så returnerar ApiKeyToCheck() "null" eller ett "CompanyData".
Om du får null så finns det ingen CompanyData baserat på inmatad apikey.
Om du får ett CompanyData så finns det en CompanyData baserat på inmatad apikey.

"Jämförelsen" har alltså redan gjorts här.

ηλί, ηλί, λαμά σαβαχθανί!?

Trädvy Permalänk
Medlem
Registrerad
Okt 2012
Skrivet av Leedow:

@joakimR:

Som det ser ut just nu så returnerar ApiKeyToCheck() "null" eller ett "CompanyData".
Om du får null så finns det ingen CompanyData baserat på inmatad apikey.
Om du får ett CompanyData så finns det en CompanyData baserat på inmatad apikey.

"Jämförelsen" har alltså redan gjorts här.

Då hänger jag med i ditt tidigare inlägg där du pratade om metoderna ! Ska kika närmre på detta. Stort tack!

Trädvy Permalänk
Medlem
Registrerad
Okt 2012

@Leedow:

Jag prövade att ändra om koden till följande, så som du föreslog;

public class APIKeyMessageHandlerMiddleware { private readonly ApplicationDbContext _db; public async Task<ActionResult<CompanyData>> ApiKeyToCheck(string GetCompanyApiKey) { CompanyData companyApiKey = await _db.Companydatas.SingleOrDefaultAsync(x => x.ApiKey == GetCompanyApiKey); return companyApiKey; } private RequestDelegate next; public APIKeyMessageHandlerMiddleware(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context) { bool validKey = false; var checkApiKeyExists = context.Request.Headers.ContainsKey("APIKey"); if (checkApiKeyExists) { var cd = ApiKeyToCheck(context.Request.Headers["APIKey"]); if (cd != null) { validKey = true; } .....

Vad som sker är att jag kan fortfarande hämta all data genom APIn i Postman, även utan att ange en API-nyckel.
Men när APIn dyker upp i browsern (när man kör debuggern) så får jag permission denied, och även om jag lägger till API-nyckeln i adressfältet får jag inte hämta någon data.

Trädvy Permalänk
Medlem
Plats
Falun
Registrerad
Dec 2003

@joakimR:

Vad är Postman?

Om du kör utan en apikey så ska du inte kunna hämta någon data.
Om du kör med en apikey så ska du kunna hämta en CompanyData baserat på denna apikey förutsatt att den existerar.

Att du stoppar med en apinyckel i adressfältet gör ingenting. Request.Headers innehåller headers och customheaders.

Det låter som att du borde göra den basala funktionen först, för att få ett proof-of-concept. Sen kan du bygga vidare.

ηλί, ηλί, λαμά σαβαχθανί!?

Trädvy Permalänk
Medlem
Registrerad
Aug 2002

Det du vill göra kallas bearer token auth.
Det finns redan middleware för detta, någons specielll anledning/krav som gör att du bygger din egen?

Trädvy Permalänk
Medlem
Registrerad
Sep 2007
Skrivet av joakimR:

@Leedow:

Jag prövade att ändra om koden till följande, så som du föreslog;

public class APIKeyMessageHandlerMiddleware { private readonly ApplicationDbContext _db; public async Task<ActionResult<CompanyData>> ApiKeyToCheck(string GetCompanyApiKey) { CompanyData companyApiKey = await _db.Companydatas.SingleOrDefaultAsync(x => x.ApiKey == GetCompanyApiKey); return companyApiKey; } private RequestDelegate next; public APIKeyMessageHandlerMiddleware(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context) { bool validKey = false; var checkApiKeyExists = context.Request.Headers.ContainsKey("APIKey"); if (checkApiKeyExists) { var cd = ApiKeyToCheck(context.Request.Headers["APIKey"]); if (cd != null) { validKey = true; } .....

Vad som sker är att jag kan fortfarande hämta all data genom APIn i Postman, även utan att ange en API-nyckel.
Men när APIn dyker upp i browsern (när man kör debuggern) så får jag permission denied, och även om jag lägger till API-nyckeln i adressfältet får jag inte hämta någon data.

variabeln cd kommer alltid vara en Task<ActionResult<CompanyData>> där, även om nyckeln inte finns. Med andra ord aldrig null, vilket gör att du sätter validKey = true

Kom-pa-TI-bilitet

Trädvy Permalänk
Medlem
Registrerad
Nov 2011

Glömde du bara köra await på följande eftersom ApiKeyToCheck är async?

var cd = await ApiKeyToCheck(context.Request.Headers["APIKey"]);

Helt ute och cyklar
Trädvy Permalänk
Medlem
Plats
Falun
Registrerad
Dec 2003
Skrivet av Teknocide:

variabeln cd kommer alltid vara en Task<ActionResult<CompanyData>> där, även om nyckeln inte finns. Med andra ord aldrig null, vilket gör att du sätter validKey = true

Good find!
Granskning av "cd"s egenskaper bör göras där cd.Result.Value innehåller CompanyData, om allt har gått bra.

ηλί, ηλί, λαμά σαβαχθανί!?