.NET API Token Authentication/Authorization mit OpenIddict
In diesem Tutorial zeige ich wie mein einer bestehenden Web API in .NET eine Token Authentication/Authorization mit OpenIddict hinzufügt. Damit können einzelne Schnittstellen absichert oder bestimmte Ressourcen nur berechtigten Nutzern zur Verfügung gestellt werden.
.NET API Token Authentication/Authorization mit OpenIddict
Die Absicherung von Systemen durch einmaliges Login und weiterer Verwendung eines so genannten Bearer Tokens ist heute gängige Praxis. Sobald man es mit einem Stateless Service zu tun hat muss man jeden einzelnen Request authentifizieren. Um nicht jedes Mal Logindaten zu schicken oder die altbackene Variante über Cookies zu verwenden gibt es die Option mit Tokens. Bei einem erstmaligen Login wird ein solches serverseitig erstellt. Alle zukünftigen Requests werden über dieses Token realisiert.
OpenIddict hinzufügen
Ich gehe bei meinem Tutorial davon aus, dass bereits ein .NET Web API Projekt existiert. Der erste Schritt ist nun die erforderlichen NuGet Pakete dem Projekt hinzuzufügen. In der *.csproj Datei sind deshalb die beiden NuGets
- OpenIddict.AspNetCore
- OpenIddict.EntityFrameworkCore
nötig. Bei mir sieht die Datei danach so aus:
Als nächstes muss das Service konfiguriert werden. Für die Benutzerverwaltung benötigt OpenIddict zwingend eine Datenbank. Eine solche wird üblicherweise ohnehin von einer API verwendet. In meinem Fall sogar eine PostgreSQL Datenbank, in den meisten Fällen wohl eine MSSQL Datenbank. Mit der Zeile
options.UseOpenIddict();
wird definiert, dass OpenIddict nun den AppDbContext verwendet.
Als nächste muss noch das Service selber konfiguriert werden. Eine mögliche Konfiguration für das Service sieht wie folgt aus:
Genauere Informationen über die einzelnen Optionen findet man in der OpenIddict Dokumentation. In der letzten Zeile wird noch ein Service „Worker“ hinzugefügt. Dieses füllt die Datenbank mit einem Beispieluser. Die Worker.cs sieht wie folgt aus:
public class Worker : IHostedService { private readonly IServiceProvider _serviceProvider; public Worker(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; public async Task StartAsync(CancellationToken cancellationToken) { using var scope = _serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); await context.Database.EnsureCreatedAsync(); var manager = scope.ServiceProvider.GetRequiredService(); if (await manager.FindByClientIdAsync("server") is null) { await manager.CreateAsync(new OpenIddictApplicationDescriptor { ClientId = "client", ClientSecret = "SuperSecretPassword", DisplayName = "API Client", Permissions = { Permissions.Endpoints.Token, Permissions.GrantTypes.ClientCredentials } }); } if (await manager.FindByClientIdAsync("blazorclient") is null) { await manager.CreateAsync(new OpenIddictApplicationDescriptor { ClientId = "blazorclient", ClientSecret = "539e9b2b-6cfc-428b-9a94-88b29e895ba7", DisplayName = "Racing Manager Client", Permissions = { Permissions.Endpoints.Token, Permissions.GrantTypes.ClientCredentials } }); } } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; }
Der relevante Teil ist der CreateAsync Bereich in dem ein neuer Benutzer angelegt wird. Für das Login später sind die ClientId und das ClientSecret wichtig. Für den Modus Permissions.GranTypes.ClientCredentials und Permissions.Endpoints.Tokens. Damit loggt sich der Client einmalig mit ClientId und ClientSecret ein und bekommt für die weiteren Abfragen ein Token. Das ist eine Möglichkeit OpenIddict zu verwenden, die Dokumentation zeigt noch viele weitere mögliche Einstellungen dazu.
Controller konfigurieren
Damit die API abgesichert werden kann benötigen wir einen zusätzlichen Controller. Einen Endpunkt über den sich der User Authorisiert und dafür ein Token erhält. Wie so etwas ausgehen kann zeigen die Samples auf der GitHub Seite von OpenIddict. Ein solcher Controller könnte so aussehen:
Der Controller muss zwingend das AllowAnonymous Attribut haben, der Endpunkt muss ohne Autorisation aufrufbar sein, schließlich muss der unangemeldete Benutzer erst einmal ein Token beantragen. Der andere Teil ist die Uri unter der dieser Controller erreichbar sein soll. Siehe dazu auch die SetTokenEndpointUris Funktion bei den Optionen vom Service.
Die Endpunkte die nun einer Authorization unterliegen und von denen man nun nur eine sinnvolle Antwort mit einem Token erwarten kann werden nun mit dem Authorize Attribut dekoriert. Das sieht beispielsweise so aus (gilt für den kompletten Controller):
Die Implementierung eines Endpunkts sieht für den angegebenen Controller beispielsweise so aus:
Die Get Abfrage kann nun auch eine 401 Antwort ergeben.
Fazit
Ich habe gezeigt welche Schritte nötig sind um mit OpenIddict eine Web API abzusichern. In wenigen Minuten ist die Schnittstelle um ein Sicherheitsmerkmal reicher, danke den mmfangreichen Konfigurationsmöglichkeiten bietet die Bibliothekt für alle Eventualitäten die nötige Flexibilität.