Benutzerverwaltung: IdentityServer aus GitHub installieren

Handy Frau Internet

Der IdentityServer ermöglicht eine einmalige und damit komfortable Authentifizierung für mehrere Anwendungen. busitec-CEO Hennig Eiben hat das Framework im Rahmen eines Kundenprojekts eingeführt.

Wenn viele Anwendungen verschiedenen Mandanten zur Verfügung gestellt werden müssen und gleichzeitig eine zentrale Authentifizierung verwendet werden soll, ist der IdentityServer die richtige Wahl. Damit soll erreicht werden, dass man sich nur einmal anmelden und sich für jede Anwendung nicht erneut anmelden muss. Außerdem sollte für die Mandanten, die bereits über ein Active Directory (AD) verfügen, dieses als Basis für die Anmeldung herangezogen werden. Allerdings müssen auch Mandanten berücksichtigt werden, die bisher noch kein AD haben. Oder der Zugriff soll auch Personen ermöglicht werden, die nicht Teil des AD sind wie zum Beispiel Mitarbeiter*innen externer Unternehmen.

Steuerung von Zugriffen mit dem IdentityServer

Alle diese Anforderungen lassen sich mit dem IdentityServer umsetzen. Zusätzlich kann darüber auch gesteuert werden, welche Anwender*innen auf welche Anwendung zugreifen können beziehungsweise welches Frontend auf welches Backend zugreifen kann. Dies kann über die Zuweisung von Claims erfolgen, die den Benutzern zugeordnet werden.

Um den IdentityServer verwenden zu können, können bereits fertige Templates genutzt werden, die einige Boilerplate Codes umgesetzt haben. Bei dem Template des IdentityServer Secure gibt es bereits eine UI mit einem Account Controller zum Einloggen.

Installation von Templates

Zunächst muss man das Template beziehungsweise die Templates für den IdentityServer installieren:

dotnet new -i IdentityServer4.Templates

Diese Templates basieren auf dem IdentityServer 4.x und ASP.NET Core 3.1. In diesem Fall haben wir die Vorlage „IdentityServer4 with AdminUI“ (is4admin) verwendet.

md EnterpriseApplication
cd EnterpriseApplication
md Common.Auth
cd Common.Auth

dotnet new is4admin -n Common.Auth.Api

dotnet new sln -n Common.Auth
dotnet sln add .\Common.Auth.Api\Common.Auth.Api.csproj

Damit haben wir eine Lösung mit einem ersten lauffähigen IdentityServer. Out oft the box (OOTB) wird von dem Template eine SQLite-Datenbank angelegt – allerdings erst seit einem kürzlichen Update des Templates. Als wir mit dem Projekt starteten, war das OOTB-Verhalten, dass alle Konfigurationsdaten in Memory gehalten wurden. Dazu wird in ConfigureServices Folgendes eingerichtet:

services.AddIdentityServer()
    .AddInMemoryClients(Config.Clients)
    .AddInMemoryIdentityResources(Config.Ids)
    .AddInMemoryApiResources(Config.Apis)
    .AddTestUsers(Config.Testuser)
    .AddDeveloperSigningCredential();

Die Daten für die Konfiguration stammen dabei aus einer Config.cs (die Daten stammen aus einem Beispiel-Projekt, das ich im Rahmen des Projekts entwickelt habe):

public static class Config
{
    public static IEnumerable<ApiResource> Apis =>
        new[]
        {
            new ApiResource("todo", "Todo API")
            {
                UserClaims = new List<string> {"EA_Role","Todo_Tenant"},
                ApiSecrets = new List<Secret> {new Secret("scopeSecret".Sha256())},
                Scopes = new List<Scope> {new Scope("todo.create"), new Scope("todo.delete")}
            }
        };

    public static IEnumerable<Client> Clients =>
        new[]
        {
            new Client
            {
                ClientId = "mvc",
                ClientSecrets = {new Secret("secret".Sha256())},
                AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
                RequireConsent = false,
                RequirePkce = true,
                RedirectUris
                    = {"http://localhost:51725/signin-oidc", "https://localhost:44360/signin-oidc"},
                FrontChannelLogoutUri = "https://localhost:44360/signout-oidc",
                PostLogoutRedirectUris =
                {
                    "http://localhost:51725/signout-oidc",
                    "https://localhost:44360/signout-oidc"
                },
                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.Email,
                    "todo.create",
                    "EA_Role",
                    "role"
                },
                AccessTokenType = AccessTokenType.Reference
            },
            new Client
            {
                ClientId = "oauthClient",
                AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
                RedirectUris = { "http://localhost:52882/swagger/oauth2-redirect.html"},
                ClientSecrets = new List<Secret> {new Secret("superSecretPassword".Sha256())},
                AllowedScopes = new List<string> {"todo.create", 
                    IdentityServerConstants.StandardScopes.Email },
                AllowedCorsOrigins = { "http://localhost:52882" }
            },
            new Client()
            {
                ClientId = "react",
                ClientName = "Web.React Frontend",
                AllowedGrantTypes = GrantTypes.Code,
                RequireConsent = false,
                RequirePkce = false,
                RequireClientSecret = false,
                PostLogoutRedirectUris = { "http://localhost:61933/signout-oidc", "https://localhost:44366/signout-oidc"},
                RedirectUris = { "http://localhost:61933/signin-oidc", "https://localhost:44366/signin-oidc" },
                FrontChannelLogoutUri = "https://localhost:61933/signout-oidc",
                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.Email,
                    "todo.create",
                    "EA",
                    "role","Todo_Tenant"
                },
                AllowedCorsOrigins={"http://localhost:61933", "https://localhost:44366" },
            }
        };

    public static IEnumerable<IdentityResource> Ids =>
        new IdentityResource[]
        {
            new IdentityResources.OpenId(), 
            new IdentityResources.Profile(), 
            new IdentityResources.Email(),
            new IdentityResource()
            {
                Name = "EA",
                UserClaims = {"EA_Role"}
            }, 
            new IdentityResource()
            {
                Name = "Todo_Tenant",UserClaims = {"Todo_Tenant"}
            }, 
        };
}

Und damit wir auch ein paar Test-User zum Einloggen haben (diese Daten stammen aus dem früheren Template-Code):

public class TestUsers
{
    public static List<TestUser> Users = new List<TestUser>
    {
        new TestUser{SubjectId = "818727", Username = "alice", Password = "alice", 
            Claims = 
            {
                new Claim(JwtClaimTypes.Name, "Alice Smith"),
                new Claim(JwtClaimTypes.GivenName, "Alice"),
                new Claim(JwtClaimTypes.FamilyName, "Smith"),
                new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
                new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
                new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)
            }
        },
        new TestUser{SubjectId = "88421113", Username = "bob", Password = "bob", 
            Claims = 
            {
                new Claim(JwtClaimTypes.Name, "Bob Smith"),
                new Claim(JwtClaimTypes.GivenName, "Bob"),
                new Claim(JwtClaimTypes.FamilyName, "Smith"),
                new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
                new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
                new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json),
                new Claim("location", "somewhere")
            }
        }
    };
}

Damit steht die erste Version unseres Backends zur Authentifizierung. Man kann sich nun als „bob“ oder „alice“ anmelden und die entsprechenden Claims ansehen.


Nach oben scrollen