Freigeben über


.NET Aspire Keycloak Integration (Vorschau)

umfasst:Hostingintegration und Client-Integration

Keycloak ist eine Open Source Identity and Access Management-Lösung für moderne Anwendungen und Dienste. Mit der .NET AspireKeycloak-Integration können Sie eine Verbindung zu vorhandenen Keycloak-Instanzen herstellen oder neue Instanzen aus .NET mit dem quay.io/keycloak/keycloak-Containerimageerstellen.

Hosting-Integration

Die .NET AspireKeycloak Hostintegrationsmodelle modellieren den server als KeycloakResource Typ. Um auf diese Typen und APIs zuzugreifen, fügen Sie das NuGet-Paket 📦Aspire.Hosting.Keycloak im Projekt des App-Hosts hinzu.

dotnet add package Aspire.Hosting.Keycloak --prerelease

Weitere Informationen finden Sie unter dotnet add package oder Paketabhängigkeiten in .NET-Anwendungen verwalten.

Fügen Sie die Ressource Keycloak hinzu

Rufen Sie in Ihrem App-Hostprojekt AddKeycloak auf, um einen Keycloak Ressourcen-Generator hinzuzufügen und zurückzugeben. Verketten sie einen Aufruf an den zurückgegebenen Ressourcen-Generator, um die Keycloakzu konfigurieren.

var builder = DistributedApplication.CreateBuilder(args);

var keycloak = builder.AddKeycloak("keycloak", 8080);

var apiService = builder.AddProject<Projects.Keycloak_ApiService>("apiservice")
                        .WithReference(keycloak)
                        .WaitFor(keycloak);

builder.AddProject<Projects.Keycloak_Web>("webfrontend")
       .WithExternalHttpEndpoints()
       .WithReference(keycloak)
       .WithReference(apiService)
       .WaitFor(apiService);

// After adding all resources, run the app...

Tipp

Verwenden Sie für die lokale Entwicklung einen stabilen Port für die Keycloak Ressource (8080 im vorherigen Beispiel). Es kann sich um einen beliebigen Port handeln, aber dieser sollte stabil sein, um Probleme mit Browser-Cookies zu vermeiden, die OIDC-Token (einschließlich der Autoritäts-URL, mit Port) über die Lebensdauer des -App-Hostshinaus speichern.

Wenn .NET.NET Aspire dem App-Host ein Containerimage hinzufügt, wie im vorherigen Beispiel mit dem quay.io/keycloak/keycloak-Image gezeigt, wird eine neue Keycloak Instanz auf dem lokalen Computer erstellt. Die Keycloak-Ressource enthält Standardanmeldeinformationen:

Wenn der App-Host ausgeführt wird, wird das Kennwort im geheimen Speicher des App-Hosts gespeichert. Sie wird dem Abschnitt Parameters hinzugefügt, zum Beispiel:

{
  "Parameters:keycloak-password": "<THE_GENERATED_PASSWORD>"
}

Der Name des Parameters ist keycloak-password, aber tatsächlich wird nur der Ressourcenname mit einem -password Suffix formatiert. Weitere Informationen finden Sie unter sichere Speicherung von App-Geheimnissen in der Entwicklung in ASP.NET Core und Hinzufügen der Ressource Keycloak.

Die WithReference-Methode konfiguriert eine Verbindung in ExampleProject mit dem Namen keycloak, und WaitFor weist den App-Host an, den abhängigen Dienst erst dann zu starten, wenn die keycloak-Ressource bereit ist.

Tipp

Wenn Sie lieber eine Verbindung mit einer vorhandenen Keycloak Instanz herstellen möchten, rufen Sie stattdessen AddConnectionString auf. Weitere Informationen finden Sie unter Referenzieren vorhandener Ressourcen.

Füge die Ressource Keycloak mit Datenvolumen hinzu.

Rufen Sie die WithDataVolume-Methode für die Keycloak-Ressource auf, um der Keycloak-Ressource ein Datenvolume hinzuzufügen:

var keycloak = builder.AddKeycloak("keycloak", 8080)
                      .WithDataVolume();

var apiService = builder.AddProject<Projects.Keycloak_ApiService>("apiservice")
                        .WithReference(keycloak)
                        .WaitFor(keycloak);

builder.AddProject<Projects.Keycloak_Web>("webfrontend")
       .WithExternalHttpEndpoints()
       .WithReference(keycloak)
       .WithReference(apiService)
       .WaitFor(apiService);

// After adding all resources, run the app...

Das Datenvolume wird verwendet, um die Keycloak Daten außerhalb des Lebenszyklus des Containers zu speichern. Das Datenvolumen wird im Pfad /opt/keycloak/data im Container Keycloak bereitgestellt, und wenn kein name Parameter bereitgestellt wird, wird der Name zufällig generiert. Weitere Informationen zu Datenvolumes und Details dazu, warum sie gegenüber Bind-Mountsbevorzugt werden, finden Sie in der Docker-Dokumentation: Volumes.

Warnung

Die Administratoranmeldeinformationen werden im Datenvolume gespeichert. Wenn Sie ein Datenvolume verwenden und sich die Anmeldeinformationen ändern, funktioniert dies nicht mehr, bis Sie das Volume löschen.

Füge Keycloak-Ressource mit Daten-Bind-Mount hinzu

Rufen Sie die WithDataBindMount-Methode auf, um der Keycloak-Ressource eine Datenbindung hinzuzufügen:

var keycloak = builder.AddKeycloak("keycloak", 8080)
                      .WithDataBindMount(@"C:\Keycloak\Data");

var apiService = builder.AddProject<Projects.Keycloak_ApiService>("apiservice")
                        .WithReference(keycloak)
                        .WaitFor(keycloak);

builder.AddProject<Projects.Keycloak_Web>("webfrontend")
       .WithExternalHttpEndpoints()
       .WithReference(keycloak)
       .WithReference(apiService)
       .WaitFor(apiService);

// After adding all resources, run the app...

Wichtig

Daten--Bind-Mounts haben im Vergleich zu -Volumeseine eingeschränkte Funktionalität. Diese bieten eine bessere Leistung, Portabilität und Sicherheit, wodurch sie für Produktionsumgebungen besser geeignet sind. Bind-Einhängepunkte ermöglichen jedoch direkten Zugriff und die Bearbeitung von Dateien auf dem Hostsystem, was ideal für die Entwicklung und das Testen ist, wo Echtzeitänderungen erforderlich sind.

Datenbindungs-Bereitstellungen basieren auf dem Dateisystem des Hostcomputers, um die Keycloak Daten über Containerneustarts hinweg beizubehalten. Der Datenbindemount wird auf dem Pfad C:\Keycloak\Data unter Windows (oder /Keycloak/Data auf Unix) auf dem Hostcomputer im Container Keycloak bereitgestellt. Weitere Informationen zu Daten-Bind-Mounts finden Sie in den Docker-Dokumenten: Bind-Mounts.

Füge die Ressource Keycloak mit Parametern hinzu

Wenn Sie explizit den Administratorbenutzernamen und das vom Containerimage verwendete Kennwort angeben möchten, können Sie diese Anmeldeinformationen als Parameter angeben. Betrachten Sie das folgende alternative Beispiel:

var builder = DistributedApplication.CreateBuilder(args);

var username = builder.AddParameter("username");
var password = builder.AddParameter("password", secret: true);

var keycloak = builder.AddKeycloak("keycloak", 8080, username, password);

var apiService = builder.AddProject<Projects.Keycloak_ApiService>("apiservice")
                        .WithReference(keycloak)
                        .WaitFor(keycloak);

builder.AddProject<Projects.Keycloak_Web>("webfrontend")
       .WithExternalHttpEndpoints()
       .WithReference(keycloak)
       .WithReference(apiService)
       .WaitFor(apiService);

// After adding all resources, run the app...

Die Parameter username und password werden in der Regel als Umgebungsvariablen oder geheime Schlüssel bereitgestellt. Die Parameter werden verwendet, um die KEYCLOAK_ADMIN- und KEYCLOAK_ADMIN_PASSWORD Umgebungsvariablen im Container festzulegen. Weitere Informationen zum Bereitstellen von Parametern finden Sie unter externe Parameter.

Fügen Sie die Ressource Keycloak mit dem Bereichsimport hinzu.

Rufen Sie zum Importieren eines Bereichs in Keycloakdie WithRealmImport-Methode auf:

var builder = DistributedApplication.CreateBuilder(args);

var keycloak = builder.AddKeycloak("keycloak", 8080)
                      .WithDataVolume()
                      .WithRealmImport("./Realms");

var apiService = builder.AddProject<Projects.AspireApp_ApiService>("apiservice")
                        .WithReference(keycloak)
                        .WaitFor(keycloak);

builder.AddProject<Projects.AspireApp_Web>("webfrontend")
       .WithExternalHttpEndpoints()
       .WithReference(keycloak)
       .WithReference(apiService)
       .WaitFor(apiService);

builder.Build().Run();

Die Bereichsimportdateien werden an /opt/keycloak/data/import im Container Keycloak eingehängt. Bereichsimportdateien sind JSON-Dateien, die die Bereichskonfiguration darstellen. Weitere Informationen zum Bereichsimport finden Sie unter Keycloak Dokumente: Importieren eines Bereichs.

Die folgende JSON-Datei könnte beispielsweise dem App-Hostprojekt in einem /Realms Ordner hinzugefügt werden, um als Quellbereichskonfigurationsdatei zu dienen:


Beispiel zum Umschalten des Bereichs JSON.

{
    "id": "86683c73-be28-4380-a014-6316c0404192",
    "realm": "WeatherShop",
    "notBefore": 0,
    "defaultSignatureAlgorithm": "RS256",
    "revokeRefreshToken": false,
    "refreshTokenMaxReuse": 0,
    "accessTokenLifespan": 300,
    "accessTokenLifespanForImplicitFlow": 900,
    "ssoSessionIdleTimeout": 1800,
    "ssoSessionMaxLifespan": 36000,
    "ssoSessionIdleTimeoutRememberMe": 0,
    "ssoSessionMaxLifespanRememberMe": 0,
    "offlineSessionIdleTimeout": 2592000,
    "offlineSessionMaxLifespanEnabled": false,
    "offlineSessionMaxLifespan": 5184000,
    "clientSessionIdleTimeout": 0,
    "clientSessionMaxLifespan": 0,
    "clientOfflineSessionIdleTimeout": 0,
    "clientOfflineSessionMaxLifespan": 0,
    "accessCodeLifespan": 60,
    "accessCodeLifespanUserAction": 300,
    "accessCodeLifespanLogin": 1800,
    "actionTokenGeneratedByAdminLifespan": 43200,
    "actionTokenGeneratedByUserLifespan": 300,
    "oauth2DeviceCodeLifespan": 600,
    "oauth2DevicePollingInterval": 5,
    "enabled": true,
    "sslRequired": "external",
    "registrationAllowed": true,
    "registrationEmailAsUsername": false,
    "rememberMe": false,
    "verifyEmail": false,
    "loginWithEmailAllowed": true,
    "duplicateEmailsAllowed": false,
    "resetPasswordAllowed": false,
    "editUsernameAllowed": false,
    "bruteForceProtected": false,
    "permanentLockout": false,
    "maxTemporaryLockouts": 0,
    "maxFailureWaitSeconds": 900,
    "minimumQuickLoginWaitSeconds": 60,
    "waitIncrementSeconds": 60,
    "quickLoginCheckMilliSeconds": 1000,
    "maxDeltaTimeSeconds": 43200,
    "failureFactor": 30,
    "roles": {
        "realm": [
            {
                "id": "79e15e0c-7084-4595-9066-c852bc5a6aca",
                "name": "uma_authorization",
                "description": "${role_uma_authorization}",
                "composite": false,
                "clientRole": false,
                "containerId": "86683c73-be28-4380-a014-6316c0404192",
                "attributes": {}
            },
            {
                "id": "f2bd959d-ed9d-4409-af6d-206a4a52cc23",
                "name": "default-roles-weathershop",
                "description": "${role_default-roles}",
                "composite": true,
                "composites": {
                    "realm": [ "offline_access", "uma_authorization" ],
                    "client": {
                        "account": [ "view-profile", "manage-account" ]
                    }
                },
                "clientRole": false,
                "containerId": "86683c73-be28-4380-a014-6316c0404192",
                "attributes": {}
            },
            {
                "id": "5e1d3cf6-c7ac-478d-a70c-4299abf58490",
                "name": "offline_access",
                "description": "${role_offline-access}",
                "composite": false,
                "clientRole": false,
                "containerId": "86683c73-be28-4380-a014-6316c0404192",
                "attributes": {}
            }
        ],
        "client": {
            "realm-management": [
                {
                    "id": "fe6e42fe-8629-40da-9afe-1179fc964988",
                    "name": "manage-users",
                    "description": "${role_manage-users}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "f82abb6c-239c-4533-afbd-a7aa03937204",
                    "name": "view-users",
                    "description": "${role_view-users}",
                    "composite": true,
                    "composites": {
                        "client": {
                            "realm-management": [ "query-groups", "query-users" ]
                        }
                    },
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "1eb57351-1302-45e5-924a-9b0dc337a2bb",
                    "name": "view-events",
                    "description": "${role_view-events}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "df3a077e-9bd4-4924-8281-cab7c7fd73e3",
                    "name": "manage-authorization",
                    "description": "${role_manage-authorization}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "d9fcb43a-3bad-492c-9af9-f199a6382064",
                    "name": "query-groups",
                    "description": "${role_query-groups}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "1dbdaf2b-a29c-4c54-86b7-d7c338e7672f",
                    "name": "realm-admin",
                    "description": "${role_realm-admin}",
                    "composite": true,
                    "composites": {
                        "client": {
                            "realm-management": [ "view-users", "manage-users", "view-events", "query-groups", "manage-authorization", "query-users", "manage-realm", "view-identity-providers", "create-client", "view-authorization", "query-clients", "view-clients", "query-realms", "impersonation", "view-realm", "manage-events", "manage-identity-providers", "manage-clients" ]
                        }
                    },
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "4c1ff7e3-cc1d-4b1d-a88f-fb71416c742a",
                    "name": "query-users",
                    "description": "${role_query-users}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "284b35f8-5bc2-4482-8769-81d3594df5a3",
                    "name": "manage-realm",
                    "description": "${role_manage-realm}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "7e872c38-8a22-469f-92ca-ec67e95d3c33",
                    "name": "view-identity-providers",
                    "description": "${role_view-identity-providers}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "9f4c2563-7575-461e-b2c8-b2b87f314cb9",
                    "name": "create-client",
                    "description": "${role_create-client}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "8f92f45c-bfa0-4a66-9812-334fe223c8be",
                    "name": "query-clients",
                    "description": "${role_query-clients}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "2a0143cf-ad90-4f68-bcb2-a50aa358b070",
                    "name": "view-authorization",
                    "description": "${role_view-authorization}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "95cc13bf-1342-445a-99fd-141522a7e777",
                    "name": "view-clients",
                    "description": "${role_view-clients}",
                    "composite": true,
                    "composites": {
                        "client": {
                            "realm-management": [ "query-clients" ]
                        }
                    },
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "109f4b83-ba7d-4036-91e7-7e169cd4c30c",
                    "name": "query-realms",
                    "description": "${role_query-realms}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "17bcb2b7-3a35-4089-85ea-1d034303b5d6",
                    "name": "impersonation",
                    "description": "${role_impersonation}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "21c51846-5f22-4318-82b7-9e64e2d256f4",
                    "name": "view-realm",
                    "description": "${role_view-realm}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "a0599e32-b53b-43bf-a7f6-ac0507ed277d",
                    "name": "manage-events",
                    "description": "${role_manage-events}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "e732e665-efb7-4df0-8843-b22bf2fe4717",
                    "name": "manage-identity-providers",
                    "description": "${role_manage-identity-providers}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                },
                {
                    "id": "6d1b10f2-4c51-4279-8418-d4b82c17f203",
                    "name": "manage-clients",
                    "description": "${role_manage-clients}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "0aa2db92-8cc4-490f-a084-55f5b889613a",
                    "attributes": {}
                }
            ],
            "WeatherWeb": [],
            "security-admin-console": [],
            "admin-cli": [],
            "account-console": [],
            "broker": [
                {
                    "id": "7184260f-55c4-454a-bf67-dade5b74df7e",
                    "name": "read-token",
                    "description": "${role_read-token}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "db2ab30c-b83b-499e-9545-decdc906a372",
                    "attributes": {}
                }
            ],
            "Postman": [],
            "weather.api": [],
            "account": [
                {
                    "id": "b4a01a53-3ed0-4e96-8fd1-efb0c143a45d",
                    "name": "view-groups",
                    "description": "${role_view-groups}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "65816a45-48d3-4856-b052-c65cb03881d3",
                    "attributes": {}
                },
                {
                    "id": "e9af1e5f-c0a5-4515-a77a-38fec79135d0",
                    "name": "delete-account",
                    "description": "${role_delete-account}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "65816a45-48d3-4856-b052-c65cb03881d3",
                    "attributes": {}
                },
                {
                    "id": "526cc4f7-6cf8-4f2b-8241-de0e60d2fd47",
                    "name": "manage-consent",
                    "description": "${role_manage-consent}",
                    "composite": true,
                    "composites": {
                        "client": {
                            "account": [ "view-consent" ]
                        }
                    },
                    "clientRole": true,
                    "containerId": "65816a45-48d3-4856-b052-c65cb03881d3",
                    "attributes": {}
                },
                {
                    "id": "cf93d42f-ffd9-4b3f-bf8d-55aa934f2fe3",
                    "name": "view-applications",
                    "description": "${role_view-applications}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "65816a45-48d3-4856-b052-c65cb03881d3",
                    "attributes": {}
                },
                {
                    "id": "f3b44155-fe06-4fea-8b8f-6954f54d48bb",
                    "name": "view-profile",
                    "description": "${role_view-profile}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "65816a45-48d3-4856-b052-c65cb03881d3",
                    "attributes": {}
                },
                {
                    "id": "65a74b6a-a00f-46b6-8ead-6c051e78c37e",
                    "name": "manage-account",
                    "description": "${role_manage-account}",
                    "composite": true,
                    "composites": {
                        "client": {
                            "account": [ "manage-account-links" ]
                        }
                    },
                    "clientRole": true,
                    "containerId": "65816a45-48d3-4856-b052-c65cb03881d3",
                    "attributes": {}
                },
                {
                    "id": "c914cc47-8a49-4f30-9851-6f639c4e7adf",
                    "name": "manage-account-links",
                    "description": "${role_manage-account-links}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "65816a45-48d3-4856-b052-c65cb03881d3",
                    "attributes": {}
                },
                {
                    "id": "261d0db4-28c7-4900-a156-01ab4e2483e5",
                    "name": "view-consent",
                    "description": "${role_view-consent}",
                    "composite": false,
                    "clientRole": true,
                    "containerId": "65816a45-48d3-4856-b052-c65cb03881d3",
                    "attributes": {}
                }
            ]
        }
    },
    "groups": [],
    "defaultRole": {
        "id": "f2bd959d-ed9d-4409-af6d-206a4a52cc23",
        "name": "default-roles-weathershop",
        "description": "${role_default-roles}",
        "composite": true,
        "clientRole": false,
        "containerId": "86683c73-be28-4380-a014-6316c0404192"
    },
    "requiredCredentials": [ "password" ],
    "otpPolicyType": "totp",
    "otpPolicyAlgorithm": "HmacSHA1",
    "otpPolicyInitialCounter": 0,
    "otpPolicyDigits": 6,
    "otpPolicyLookAheadWindow": 1,
    "otpPolicyPeriod": 30,
    "otpPolicyCodeReusable": false,
    "otpSupportedApplications": [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ],
    "localizationTexts": {},
    "webAuthnPolicyRpEntityName": "keycloak",
    "webAuthnPolicySignatureAlgorithms": [ "ES256" ],
    "webAuthnPolicyRpId": "",
    "webAuthnPolicyAttestationConveyancePreference": "not specified",
    "webAuthnPolicyAuthenticatorAttachment": "not specified",
    "webAuthnPolicyRequireResidentKey": "not specified",
    "webAuthnPolicyUserVerificationRequirement": "not specified",
    "webAuthnPolicyCreateTimeout": 0,
    "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
    "webAuthnPolicyAcceptableAaguids": [],
    "webAuthnPolicyExtraOrigins": [],
    "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
    "webAuthnPolicyPasswordlessSignatureAlgorithms": [ "ES256" ],
    "webAuthnPolicyPasswordlessRpId": "",
    "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
    "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
    "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
    "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
    "webAuthnPolicyPasswordlessCreateTimeout": 0,
    "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
    "webAuthnPolicyPasswordlessAcceptableAaguids": [],
    "webAuthnPolicyPasswordlessExtraOrigins": [],
    "scopeMappings": [
        {
            "clientScope": "offline_access",
            "roles": [ "offline_access" ]
        }
    ],
    "clientScopeMappings": {
        "account": [
            {
                "client": "account-console",
                "roles": [ "manage-account", "view-groups" ]
            }
        ]
    },
    "clients": [
        {
            "id": "bd03dd61-71bf-4f50-acfa-bfc2444ee1d2",
            "clientId": "Postman",
            "name": "",
            "description": "",
            "rootUrl": "",
            "adminUrl": "",
            "baseUrl": "",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [ "https://oauth.pstmn.io/v1/callback" ],
            "webOrigins": [ "https://oauth.pstmn.io" ],
            "notBefore": 0,
            "bearerOnly": false,
            "consentRequired": false,
            "standardFlowEnabled": true,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": false,
            "serviceAccountsEnabled": false,
            "publicClient": true,
            "frontchannelLogout": true,
            "protocol": "openid-connect",
            "attributes": {
                "oidc.ciba.grant.enabled": "false",
                "client.secret.creation.time": "1718111570",
                "backchannel.logout.session.required": "true",
                "post.logout.redirect.uris": "+",
                "oauth2.device.authorization.grant.enabled": "false",
                "display.on.consent.screen": "false",
                "backchannel.logout.revoke.offline.tokens": "false"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": true,
            "nodeReRegistrationTimeout": -1,
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "weather:all", "microprofile-jwt" ]
        },
        {
            "id": "016c17d1-8e0f-4a67-9116-86b4691ba99c",
            "clientId": "WeatherWeb",
            "name": "",
            "description": "",
            "rootUrl": "",
            "adminUrl": "",
            "baseUrl": "",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [ "https://localhost:7085/signin-oidc" ],
            "webOrigins": [ "https://localhost:7085" ],
            "notBefore": 0,
            "bearerOnly": false,
            "consentRequired": false,
            "standardFlowEnabled": true,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": false,
            "serviceAccountsEnabled": false,
            "publicClient": true,
            "frontchannelLogout": true,
            "protocol": "openid-connect",
            "attributes": {
                "oidc.ciba.grant.enabled": "false",
                "post.logout.redirect.uris": "https://localhost:7085/signout-callback-oidc",
                "oauth2.device.authorization.grant.enabled": "false",
                "backchannel.logout.session.required": "true",
                "backchannel.logout.revoke.offline.tokens": "false"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": true,
            "nodeReRegistrationTimeout": -1,
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "weather:all", "microprofile-jwt" ]
        },
        {
            "id": "65816a45-48d3-4856-b052-c65cb03881d3",
            "clientId": "account",
            "name": "${client_account}",
            "rootUrl": "${authBaseUrl}",
            "baseUrl": "/realms/WeatherShop/account/",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [ "/realms/WeatherShop/account/*" ],
            "webOrigins": [],
            "notBefore": 0,
            "bearerOnly": false,
            "consentRequired": false,
            "standardFlowEnabled": true,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": false,
            "serviceAccountsEnabled": false,
            "publicClient": true,
            "frontchannelLogout": false,
            "protocol": "openid-connect",
            "attributes": {
                "post.logout.redirect.uris": "+"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": false,
            "nodeReRegistrationTimeout": 0,
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "microprofile-jwt" ]
        },
        {
            "id": "437fda77-3ba1-4d7b-b192-808e4e62833b",
            "clientId": "account-console",
            "name": "${client_account-console}",
            "rootUrl": "${authBaseUrl}",
            "baseUrl": "/realms/WeatherShop/account/",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [ "/realms/WeatherShop/account/*" ],
            "webOrigins": [],
            "notBefore": 0,
            "bearerOnly": false,
            "consentRequired": false,
            "standardFlowEnabled": true,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": false,
            "serviceAccountsEnabled": false,
            "publicClient": true,
            "frontchannelLogout": false,
            "protocol": "openid-connect",
            "attributes": {
                "post.logout.redirect.uris": "+",
                "pkce.code.challenge.method": "S256"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": false,
            "nodeReRegistrationTimeout": 0,
            "protocolMappers": [
                {
                    "id": "e4606d8a-a581-402c-9290-4e3b988f2090",
                    "name": "audience resolve",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-audience-resolve-mapper",
                    "consentRequired": false,
                    "config": {}
                }
            ],
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "microprofile-jwt" ]
        },
        {
            "id": "f13fd042-6931-4032-a0ba-f63b364f8d56",
            "clientId": "admin-cli",
            "name": "${client_admin-cli}",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [],
            "webOrigins": [],
            "notBefore": 0,
            "bearerOnly": false,
            "consentRequired": false,
            "standardFlowEnabled": false,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": true,
            "serviceAccountsEnabled": false,
            "publicClient": true,
            "frontchannelLogout": false,
            "protocol": "openid-connect",
            "attributes": {
                "post.logout.redirect.uris": "+"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": false,
            "nodeReRegistrationTimeout": 0,
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "microprofile-jwt" ]
        },
        {
            "id": "db2ab30c-b83b-499e-9545-decdc906a372",
            "clientId": "broker",
            "name": "${client_broker}",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [],
            "webOrigins": [],
            "notBefore": 0,
            "bearerOnly": true,
            "consentRequired": false,
            "standardFlowEnabled": true,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": false,
            "serviceAccountsEnabled": false,
            "publicClient": false,
            "frontchannelLogout": false,
            "protocol": "openid-connect",
            "attributes": {
                "post.logout.redirect.uris": "+"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": false,
            "nodeReRegistrationTimeout": 0,
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "microprofile-jwt" ]
        },
        {
            "id": "0aa2db92-8cc4-490f-a084-55f5b889613a",
            "clientId": "realm-management",
            "name": "${client_realm-management}",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [],
            "webOrigins": [],
            "notBefore": 0,
            "bearerOnly": true,
            "consentRequired": false,
            "standardFlowEnabled": true,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": false,
            "serviceAccountsEnabled": false,
            "publicClient": false,
            "frontchannelLogout": false,
            "protocol": "openid-connect",
            "attributes": {
                "post.logout.redirect.uris": "+"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": false,
            "nodeReRegistrationTimeout": 0,
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "microprofile-jwt" ]
        },
        {
            "id": "e0cc9cef-924e-4799-a921-4811f3bb5d65",
            "clientId": "security-admin-console",
            "name": "${client_security-admin-console}",
            "rootUrl": "${authAdminUrl}",
            "baseUrl": "/admin/WeatherShop/console/",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "redirectUris": [ "/admin/WeatherShop/console/*" ],
            "webOrigins": [ "+" ],
            "notBefore": 0,
            "bearerOnly": false,
            "consentRequired": false,
            "standardFlowEnabled": true,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": false,
            "serviceAccountsEnabled": false,
            "publicClient": true,
            "frontchannelLogout": false,
            "protocol": "openid-connect",
            "attributes": {
                "post.logout.redirect.uris": "+",
                "pkce.code.challenge.method": "S256"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": false,
            "nodeReRegistrationTimeout": 0,
            "protocolMappers": [
                {
                    "id": "254ac20c-6701-4095-82c6-6abd6669b9de",
                    "name": "locale",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "locale",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "locale",
                        "jsonType.label": "String"
                    }
                }
            ],
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "microprofile-jwt" ]
        },
        {
            "id": "4b5953fd-b218-41be-b061-58f37c1c7d26",
            "clientId": "weather.api",
            "name": "",
            "description": "",
            "rootUrl": "",
            "adminUrl": "",
            "baseUrl": "",
            "surrogateAuthRequired": false,
            "enabled": true,
            "alwaysDisplayInConsole": false,
            "clientAuthenticatorType": "client-secret",
            "secret": "**********",
            "redirectUris": [ "/*" ],
            "webOrigins": [ "/*" ],
            "notBefore": 0,
            "bearerOnly": false,
            "consentRequired": false,
            "standardFlowEnabled": true,
            "implicitFlowEnabled": false,
            "directAccessGrantsEnabled": false,
            "serviceAccountsEnabled": false,
            "publicClient": false,
            "frontchannelLogout": true,
            "protocol": "openid-connect",
            "attributes": {
                "oidc.ciba.grant.enabled": "false",
                "client.secret.creation.time": "1718111354",
                "backchannel.logout.session.required": "true",
                "post.logout.redirect.uris": "+",
                "oauth2.device.authorization.grant.enabled": "false",
                "backchannel.logout.revoke.offline.tokens": "false"
            },
            "authenticationFlowBindingOverrides": {},
            "fullScopeAllowed": true,
            "nodeReRegistrationTimeout": -1,
            "defaultClientScopes": [ "web-origins", "acr", "profile", "roles", "email" ],
            "optionalClientScopes": [ "address", "phone", "offline_access", "microprofile-jwt" ]
        }
    ],
    "clientScopes": [
        {
            "id": "2a6322a2-2f6a-469f-b3c7-d0922db4ad46",
            "name": "phone",
            "description": "OpenID Connect built-in scope: phone",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "true",
                "display.on.consent.screen": "true",
                "consent.screen.text": "${phoneScopeConsentText}"
            },
            "protocolMappers": [
                {
                    "id": "ab515c55-d65b-42d3-9d3c-18921a8df065",
                    "name": "phone number",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "phoneNumber",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "phone_number",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "bfaa5db4-137c-4824-bfae-ed77762872c2",
                    "name": "phone number verified",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "phoneNumberVerified",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "phone_number_verified",
                        "jsonType.label": "boolean"
                    }
                }
            ]
        },
        {
            "id": "52fc55cb-995e-4aa2-95ae-3b3d6601dc41",
            "name": "weather:all",
            "description": "",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "true",
                "display.on.consent.screen": "true",
                "gui.order": "",
                "consent.screen.text": ""
            },
            "protocolMappers": [
                {
                    "id": "06d03e02-1e56-4bde-911d-bcf28aeba90f",
                    "name": "weather api audience",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-audience-mapper",
                    "consentRequired": false,
                    "config": {
                        "included.client.audience": "weather.api",
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "false",
                        "id.token.claim": "false",
                        "lightweight.claim": "false",
                        "access.token.claim": "true"
                    }
                }
            ]
        },
        {
            "id": "292ded65-c85e-4c56-ad4d-8e886b9bb261",
            "name": "email",
            "description": "OpenID Connect built-in scope: email",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "true",
                "display.on.consent.screen": "true",
                "consent.screen.text": "${emailScopeConsentText}"
            },
            "protocolMappers": [
                {
                    "id": "42743882-7e0a-455e-b6a3-794ec8bf0f22",
                    "name": "email verified",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-property-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "emailVerified",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "email_verified",
                        "jsonType.label": "boolean"
                    }
                },
                {
                    "id": "b6dd2af9-e583-4d01-95fa-3f0db3ab0129",
                    "name": "email",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "email",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "email",
                        "jsonType.label": "String"
                    }
                }
            ]
        },
        {
            "id": "09a76939-4997-49f5-b88e-dfe54a2819f5",
            "name": "offline_access",
            "description": "OpenID Connect built-in scope: offline_access",
            "protocol": "openid-connect",
            "attributes": {
                "consent.screen.text": "${offlineAccessScopeConsentText}",
                "display.on.consent.screen": "true"
            }
        },
        {
            "id": "95ff8627-716e-49f0-b960-52185409d628",
            "name": "profile",
            "description": "OpenID Connect built-in scope: profile",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "true",
                "display.on.consent.screen": "true",
                "consent.screen.text": "${profileScopeConsentText}"
            },
            "protocolMappers": [
                {
                    "id": "b1ae43d1-9d52-40cd-9c6a-a8557ea63f9a",
                    "name": "picture",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "picture",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "picture",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "b9e09b82-3e67-4175-b34a-419b24a13a7f",
                    "name": "zoneinfo",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "zoneinfo",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "zoneinfo",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "06e88533-d2cc-4ae3-a25a-a17e93f69dee",
                    "name": "nickname",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "nickname",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "nickname",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "b697c055-2fb9-4985-8919-33d9f524eaa9",
                    "name": "full name",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-full-name-mapper",
                    "consentRequired": false,
                    "config": {
                        "id.token.claim": "true",
                        "introspection.token.claim": "true",
                        "access.token.claim": "true",
                        "userinfo.token.claim": "true"
                    }
                },
                {
                    "id": "70663048-2110-4271-a8f4-105e77fe2905",
                    "name": "profile",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "profile",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "profile",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "85c992ed-4971-41f1-a4b8-c6263b29dff8",
                    "name": "website",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "website",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "website",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "49e1d494-a72e-49de-a5de-8c2ad752205c",
                    "name": "birthdate",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "birthdate",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "birthdate",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "a746724f-7622-4ad4-91ef-811da6c735ad",
                    "name": "updated at",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "updatedAt",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "updated_at",
                        "jsonType.label": "long"
                    }
                },
                {
                    "id": "d647d13f-9f96-49f3-b32a-62ad63c37d0e",
                    "name": "gender",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "gender",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "gender",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "86204f6f-16ce-4c9f-9fca-f66b3f292554",
                    "name": "given name",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "firstName",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "given_name",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "f4005bd9-18d2-4456-9e7c-98c5a637f063",
                    "name": "locale",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "locale",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "locale",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "04b20a5a-1588-476c-a465-26a691320510",
                    "name": "family name",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "lastName",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "family_name",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "f812580a-7863-44c3-bcf0-c3f441f0194e",
                    "name": "middle name",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "middleName",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "middle_name",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "c2ae83c9-62c2-4a65-adef-c1d6fd126ec4",
                    "name": "username",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "username",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "preferred_username",
                        "jsonType.label": "String"
                    }
                }
            ]
        },
        {
            "id": "3ab2520f-feb4-43fd-9256-fa9f3e521aa7",
            "name": "address",
            "description": "OpenID Connect built-in scope: address",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "true",
                "display.on.consent.screen": "true",
                "consent.screen.text": "${addressScopeConsentText}"
            },
            "protocolMappers": [
                {
                    "id": "b083371e-07ae-4b01-9e8c-6a54b396359f",
                    "name": "address",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-address-mapper",
                    "consentRequired": false,
                    "config": {
                        "user.attribute.formatted": "formatted",
                        "user.attribute.country": "country",
                        "introspection.token.claim": "true",
                        "user.attribute.postal_code": "postal_code",
                        "userinfo.token.claim": "true",
                        "user.attribute.street": "street",
                        "id.token.claim": "true",
                        "user.attribute.region": "region",
                        "access.token.claim": "true",
                        "user.attribute.locality": "locality"
                    }
                }
            ]
        },
        {
            "id": "fda66a99-e8b6-49e6-9186-0a7026ec0275",
            "name": "web-origins",
            "description": "OpenID Connect scope for add allowed web origins to the access token",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "false",
                "display.on.consent.screen": "false",
                "consent.screen.text": ""
            },
            "protocolMappers": [
                {
                    "id": "4b12d4de-8c05-4251-9c8f-f801cfa3bf2a",
                    "name": "allowed web origins",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-allowed-origins-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "access.token.claim": "true"
                    }
                }
            ]
        },
        {
            "id": "581928cd-35d7-4c79-a9d1-4e1c9c8ade7e",
            "name": "acr",
            "description": "OpenID Connect scope for add acr (authentication context class reference) to the token",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "false",
                "display.on.consent.screen": "false"
            },
            "protocolMappers": [
                {
                    "id": "d664d3db-be9f-4fee-a72a-be6a295c47f5",
                    "name": "acr loa level",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-acr-mapper",
                    "consentRequired": false,
                    "config": {
                        "id.token.claim": "true",
                        "introspection.token.claim": "true",
                        "access.token.claim": "true",
                        "userinfo.token.claim": "true"
                    }
                }
            ]
        },
        {
            "id": "9ac2f136-a665-4550-ac77-cc61a1cd1e95",
            "name": "role_list",
            "description": "SAML role list",
            "protocol": "saml",
            "attributes": {
                "consent.screen.text": "${samlRoleListScopeConsentText}",
                "display.on.consent.screen": "true"
            },
            "protocolMappers": [
                {
                    "id": "040520c7-9dfb-4f9d-a93e-17e3267b1517",
                    "name": "role list",
                    "protocol": "saml",
                    "protocolMapper": "saml-role-list-mapper",
                    "consentRequired": false,
                    "config": {
                        "single": "false",
                        "attribute.nameformat": "Basic",
                        "attribute.name": "Role"
                    }
                }
            ]
        },
        {
            "id": "f0ff8363-c507-4113-adc0-47f6b346de26",
            "name": "roles",
            "description": "OpenID Connect scope for add user roles to the access token",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "false",
                "display.on.consent.screen": "true",
                "consent.screen.text": "${rolesScopeConsentText}"
            },
            "protocolMappers": [
                {
                    "id": "13aa2234-81bf-4018-a67c-d657045eac1f",
                    "name": "client roles",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-client-role-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "multivalued": "true",
                        "user.attribute": "foo",
                        "access.token.claim": "true",
                        "claim.name": "resource_access.${client_id}.roles",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "d0b63e0d-5543-41dc-baf6-1c6987d6a18d",
                    "name": "audience resolve",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-audience-resolve-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "access.token.claim": "true"
                    }
                },
                {
                    "id": "38881c98-4009-412b-b924-d36f55273f3e",
                    "name": "realm roles",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-realm-role-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "multivalued": "true",
                        "user.attribute": "foo",
                        "access.token.claim": "true",
                        "claim.name": "realm_access.roles",
                        "jsonType.label": "String"
                    }
                }
            ]
        },
        {
            "id": "b8a70a2a-a24d-4862-ad4b-dd737b60f7ce",
            "name": "microprofile-jwt",
            "description": "Microprofile - JWT built-in scope",
            "protocol": "openid-connect",
            "attributes": {
                "include.in.token.scope": "true",
                "display.on.consent.screen": "false"
            },
            "protocolMappers": [
                {
                    "id": "4eb888ff-b503-4506-b35c-ea0ac0ff3cd3",
                    "name": "groups",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-realm-role-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "multivalued": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "foo",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "groups",
                        "jsonType.label": "String"
                    }
                },
                {
                    "id": "d39896cb-3f45-4a62-a254-f7d0eb10e60a",
                    "name": "upn",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "consentRequired": false,
                    "config": {
                        "introspection.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "user.attribute": "username",
                        "id.token.claim": "true",
                        "access.token.claim": "true",
                        "claim.name": "upn",
                        "jsonType.label": "String"
                    }
                }
            ]
        }
    ],
    "defaultDefaultClientScopes": [ "role_list", "profile", "email", "roles", "web-origins", "acr" ],
    "defaultOptionalClientScopes": [ "offline_access", "address", "phone", "microprofile-jwt", "weather:all" ],
    "browserSecurityHeaders": {
        "contentSecurityPolicyReportOnly": "",
        "xContentTypeOptions": "nosniff",
        "referrerPolicy": "no-referrer",
        "xRobotsTag": "none",
        "xFrameOptions": "SAMEORIGIN",
        "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
        "xXSSProtection": "1; mode=block",
        "strictTransportSecurity": "max-age=31536000; includeSubDomains"
    },
    "smtpServer": {},
    "eventsEnabled": false,
    "eventsListeners": [ "jboss-logging" ],
    "enabledEventTypes": [],
    "adminEventsEnabled": false,
    "adminEventsDetailsEnabled": false,
    "identityProviders": [],
    "identityProviderMappers": [],
    "components": {
        "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
            {
                "id": "887848c6-60f6-47ac-ae7f-62f8a5608a4b",
                "name": "Trusted Hosts",
                "providerId": "trusted-hosts",
                "subType": "anonymous",
                "subComponents": {},
                "config": {
                    "host-sending-registration-request-must-match": [ "true" ],
                    "client-uris-must-match": [ "true" ]
                }
            },
            {
                "id": "4333143f-bf59-419a-99e2-2cce8a5d414a",
                "name": "Consent Required",
                "providerId": "consent-required",
                "subType": "anonymous",
                "subComponents": {},
                "config": {}
            },
            {
                "id": "1ac2db3a-57a1-4567-bc2b-80a2b0c96b71",
                "name": "Max Clients Limit",
                "providerId": "max-clients",
                "subType": "anonymous",
                "subComponents": {},
                "config": {
                    "max-clients": [ "200" ]
                }
            },
            {
                "id": "adb4b546-6386-46e0-8ce9-80bacbba2afe",
                "name": "Allowed Protocol Mapper Types",
                "providerId": "allowed-protocol-mappers",
                "subType": "authenticated",
                "subComponents": {},
                "config": {
                    "allowed-protocol-mapper-types": [ "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper" ]
                }
            },
            {
                "id": "241c5dea-68b9-4684-a816-80b08ef86bff",
                "name": "Full Scope Disabled",
                "providerId": "scope",
                "subType": "anonymous",
                "subComponents": {},
                "config": {}
            },
            {
                "id": "6b0e159c-33dc-492b-9d88-421973015466",
                "name": "Allowed Protocol Mapper Types",
                "providerId": "allowed-protocol-mappers",
                "subType": "anonymous",
                "subComponents": {},
                "config": {
                    "allowed-protocol-mapper-types": [ "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-full-name-mapper" ]
                }
            },
            {
                "id": "730409bc-ce7e-4b64-a870-946aeba9f65b",
                "name": "Allowed Client Scopes",
                "providerId": "allowed-client-templates",
                "subType": "anonymous",
                "subComponents": {},
                "config": {
                    "allow-default-scopes": [ "true" ]
                }
            },
            {
                "id": "0b4b8c74-20b6-4902-b971-9b97001da41e",
                "name": "Allowed Client Scopes",
                "providerId": "allowed-client-templates",
                "subType": "authenticated",
                "subComponents": {},
                "config": {
                    "allow-default-scopes": [ "true" ]
                }
            }
        ],
        "org.keycloak.keys.KeyProvider": [
            {
                "id": "c08db9a9-0d9d-4b56-96d5-7f2b1d4528df",
                "name": "rsa-generated",
                "providerId": "rsa-generated",
                "subComponents": {},
                "config": {
                    "privateKey": [ "MIIEowIBAAKCAQEAyJtAKWr1DdQmh9Nxp2LUGOrc5OA+rdXkV6+kOT21wVsIP/1bg6HekqfMySZhIxlALfegc90j0mrqkolb5s7axotTwwABwIvgxW5hHIQ4huntiZUYPUuf4m51dwyLs/GM1gSbzs9ciBKC5i4S21CQzuGp0QHpyOOn1kQZd0vYSGjpG3ewMYphJEfd60TQP74RcqASNoOaS3lU7+5SCQuiff1fSZYqYvIFmK3rcNrauTSryx6rh935ODSdYzQN0XA6g1WJK2hbBlBJJzeAj/CXXcBaw7aB1AoC7kjJ7XaYmHdC+7zIYhvNKcGtFhrMjoOVnJM9PiRMrk7ous7XAmKc6QIDAQABAoIBAC4qjm5Js1oqnfBpwJDrPVD7sfjJQ5t5azqjzQkwUrUMGF6zlZ06QhDhpY8QMlAjxkGd6JLpjE4nNVMiYeBA8Be7pjvs8zpG5qRBBf/MTP79dGFSitjGX+X6EjXi0P7JIuZ4+otybMLS8cV7ynKm/KBjzhMv28fT3oMAupSaA40MESKVD8BDR0bNQ6H3h1xrq2+81xViFsle9qcrZjJzCz/rttNi3DUct5IeJwc9Wai937L6H/BU5eBd9vQHM5raLL0iDSF5CQF2tE/j3hq8kdpRm9XRUm/0WaXOz8Il96GfSEUXLVvKnLdHu7qYbqKKBYI7B3xz8VYiHgDvBMqiBzUCgYEA9Sd/EijWpXAAaFOJk75BOu0sSjZHjoGbaLE1XWsQyM1fkFT9kxQ+8/kDiHSJal+utoEIrMWK/KdrrIjT0TXbWKCarV+ebgCt9dkTd6Or2SUku5k0TMSMoVEI2KxI6ZHvCz2Vxe8Ahbao28u8xOGRQYSs5ynvH0oBTex+RP3sv6UCgYEA0Xs55XdSn1PGazAuZYk9ZKJhCovdalYdjnJYxuSPfBgHV4CIHdhHvd/hohumMakQsnRDmJzbk/uFlqJecZtcQ/DukGylC/6dvfp6prUrVhghK+7+Bnry7zfQ9rMWslrlhf9ZTrQ8F9pssCtgAfs+Pfj0zF5DxQqFqF3wz12sJPUCgYATR5HkubV3uUEu8zLknZe/rJtJEs+501OHfjg2Ko9dW1linmx6vqLcyP6QIqoT5YZ179vgyoBNslTzcqdF0rh3VdoUPGrXN9J2fSXcyNBg+VzULA5C40oz/Y12jMYHKGTmO2el80/VNDI/Ztxnl123C1oVq+SUT1ue5zRe9KFDyQKBgBihnqsmnqZxWVFdNvdlbbyZg0OUMpLAUXVgaKPqWBzFToexa0/nEHh5DLTc/2uzb20sUo5tUzxRROHzcZt2IyEyATsmKzn/1Fh0TVuwzcmvyKa70U69wjbynzWC1VZfbcGVxtCETNSZMFJ+pylUe3sZ/N7S7rEKjbDAawJXB1jJAoGBAOZF4M9C1GLCZqt4RrF1MBI0Q+7Im45Hvr0FSck0y9xLpl9yolQREWoiCo5+WM54YMeSWu0rzBsLAjCoFpIX2cWcICsdPAnyyjIMPmuGt/uUaIWmq/ZRAsqkOW0sqxUMggjytNCtvdCyfQOcKsEHpBKrA6z+8l/3Z1RQFwan+VPs" ],
                    "certificate": [ "MIICpTCCAY0CBgGQcJfMUjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtXZWF0aGVyU2hvcDAeFw0yNDA3MDEyMzE2NTRaFw0zNDA3MDEyMzE4MzRaMBYxFDASBgNVBAMMC1dlYXRoZXJTaG9wMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyJtAKWr1DdQmh9Nxp2LUGOrc5OA+rdXkV6+kOT21wVsIP/1bg6HekqfMySZhIxlALfegc90j0mrqkolb5s7axotTwwABwIvgxW5hHIQ4huntiZUYPUuf4m51dwyLs/GM1gSbzs9ciBKC5i4S21CQzuGp0QHpyOOn1kQZd0vYSGjpG3ewMYphJEfd60TQP74RcqASNoOaS3lU7+5SCQuiff1fSZYqYvIFmK3rcNrauTSryx6rh935ODSdYzQN0XA6g1WJK2hbBlBJJzeAj/CXXcBaw7aB1AoC7kjJ7XaYmHdC+7zIYhvNKcGtFhrMjoOVnJM9PiRMrk7ous7XAmKc6QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAeDhADaUkVzu1o8CvCfU9oa6hGsJa+2qFeUPQnG7HZAQ07MjQ/RZgMMOmdhspZIuaf+Yrx/My8VDphKzNtb2eNHgFVVQDy1F1jVy9z+t7xCwX3UfjtVNMp3tMmzWUEi0pckW0mq4Bz3w0+dKPw8z0K2c19dVN1NHDViqFghB+77tO8JguwTDE8fkmXjLixDCcenBCPXjxNWmhXgOMF1wKhlq1h0+SaKt/F2P/WzyoYu6tz8qVysQvv4knB/HEGjSji+DN+uJFE4RJG+B5X+vp5LHvlYKIROQ7/aSzvCjx7zrslmcTsSTL3F1k2Ox2Hz+rGIcA8sGqbj+W5+nKxRbad" ],
                    "priority": [ "100" ]
                }
            },
            {
                "id": "9adc7ed1-f51b-48d4-9d52-96b46abefa18",
                "name": "aes-generated",
                "providerId": "aes-generated",
                "subComponents": {},
                "config": {
                    "kid": [ "eb1853e7-ac8c-4e8f-8b4f-9ea28a71da76" ],
                    "secret": [ "Jges9iPdpfx0aivf3RUUpw" ],
                    "priority": [ "100" ]
                }
            },
            {
                "id": "f4b81876-91be-46f3-9050-c351cd1531af",
                "name": "rsa-enc-generated",
                "providerId": "rsa-enc-generated",
                "subComponents": {},
                "config": {
                    "privateKey": [ "MIIEowIBAAKCAQEAlP+NrT2KVZpdrbPoTsMO7MqeXYDeJNl7IXjY0hCb7p1iB6YLOr/lA5ryk/CIHI05HRt+AEYFac87mb51SEvAa9cHjQ00v7t6hoYV1esyRmB0Nnf8AAEq9GoZxX9nUsIMcExQ3gHkF56kidYtjQSgl5SlwgdvlsjRiDP9ZJlsWTBb+8v0OCCbQLZFl93IlTZ7QlaxXoB0dCuuLNyBpELFbc0+JeB+1P/Dw5azUKGdp3ng2K9IrtDBiMh+KicpLZeBpUlqdKqyI2WvwruL1SlqH/ymWCxseRH0Z9VZ30MfW8C5fHq3qnLQp0OWDa7Re/pRbCZPabivDkZCtuWUeAVbRwIDAQABAoIBAAoioBaKuyA7kefA9yp0Zk2BMuiVXYcQLCoIuGcBrjm7BvISP21NpFxsa9fYYsneaWYrepS2LqQV7q30oLG8RWiQhfj4TwBD1n/UGyQkDZVv9jfGTaQKcEuT9BDVK8gbXxE8f7u6UTOyHOsrYInZKLtm5yedrd+J5YboUnJHZXFjmCpuyap8zJQdczUpZeLkj4bRqEHYlM1a5vfMFpr2+k4/Nqo36CCaGzIcwtYxnC002rL7ra9MaR1fy5KHtcoYQeePuPvXu1UxBU01+W1QmIsw+KmXftdtWYAMcVFkHDs+22h3mCxRhQqcxmL1LUP9FfGzrWiX4eTduw4YOkzO7+ECgYEAx732U2G7Qdp1uXsVGE7ceFcrpl/LOTaL8eEtULBiFRdrn5LOTAptpoDPB8vYCwIQSNrBWXNqTiDubOBcL7sumBK2i4Omtzk/USuMcJmSiMcHaANa/ZNK9nqtwMACqXpIPEkpofzfgBSaRYY1KxaWLbepyLjqviADV98zOI4qY28CgYEAvvbNd+C0pi5u/EkUxhRdxwCxICfkDYjArcjPU3ZBjIyAzmJ08wU+3CDm2sgts1TX+D7MPsRLqsaM8vMB+BR1xo3FfP/nqNvjYkChACtHhvkWGVE/qf3/53NNa4lKeVz8h1iV18dvLICj+V+lvUyQPuER4cA8Kzs7dObl52V6OakCgYB5bSkvPW2aNhV1QbbsRRzQZ6XYicnAqUFgNRTYRbIKwmch5hxVq81G+G1jfu+CmamOsLX0DC7m+iwXsjk4pyFHP7ELlWgnYLz2OnQxC5tCXURKXifVmdJrjt7MG65Cm10IkS2nFVRFx8CVXWY7IIsBlfK4XHoQROPjaoP38K0iLwKBgQCho6RdkR04ANu+vllQJNMP7B0Be+KENjnpn60mF1X6kr9AcoRNZCZGC698hq5wOiOoo/ccNelafz+1MU58X00lqMD+QlojSySX+N6OlxOvQs2a1nQN/sqKbcWdfZNFURkLs0b6Y3xN7gFdxsEyj0kVgEszjBUh/rwgAoWdrP6dKQKBgD2b7GG1/TPTBfOvvE7q7xa5IlTMUbBU2yY8H6XvDgfG5KUXXFpqINTOE4OjuyVwJRwE2/GQGayfZvfa+LGkW8Hy6Iz/vaa1Brjodza/2PblM5v1t96xA0WQDime8okJN0q7ocbLfQnT0+3TVMbvCjeBtOqmMOJT3EijpAqVbR6N" ],
                    "certificate": [ "MIICpTCCAY0CBgGQcJfM/jANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtXZWF0aGVyU2hvcDAeFw0yNDA3MDEyMzE2NTVaFw0zNDA3MDEyMzE4MzVaMBYxFDASBgNVBAMMC1dlYXRoZXJTaG9wMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlP+NrT2KVZpdrbPoTsMO7MqeXYDeJNl7IXjY0hCb7p1iB6YLOr/lA5ryk/CIHI05HRt+AEYFac87mb51SEvAa9cHjQ00v7t6hoYV1esyRmB0Nnf8AAEq9GoZxX9nUsIMcExQ3gHkF56kidYtjQSgl5SlwgdvlsjRiDP9ZJlsWTBb+8v0OCCbQLZFl93IlTZ7QlaxXoB0dCuuLNyBpELFbc0+JeB+1P/Dw5azUKGdp3ng2K9IrtDBiMh+KicpLZeBpUlqdKqyI2WvwruL1SlqH/ymWCxseRH0Z9VZ30MfW8C5fHq3qnLQp0OWDa7Re/pRbCZPabivDkZCtuWUeAVbRwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAUIS+ce3NPCSk7iiA4vzm1hGrEq7Q+1CwE9hq/p8oKowOEVvg68tE+yzNsYw6qM+KKdzQfmiVeT8skDhNwL+5+Oxsg9dw7KW1me+g/pjiFx1eXt/rHN5aVDz7/F3QAP0G/CUF6dVNh0ggoGhwAH74iH91apmJgDUEBVzwaYCrHDJ81nWZOGZm4MF6FFvc8Kwf/+KEefL7psH5I4BqS+gRaPFWjBnABS7WkJ879gv0Q3tHE4KXF1b3eudGFrW4rG048pqNJgxAXdoDqFR5qIi9pfuE+HCmuhPv2Xq+I7S4PpUYnUM7o0Ng+1hJsRLhiG0Kmcepy7thiJJI619miVXdF" ],
                    "priority": [ "100" ],
                    "algorithm": [ "RSA-OAEP" ]
                }
            },
            {
                "id": "c962c2d9-ac19-4a91-88b3-959c6fcfc4c4",
                "name": "hmac-generated-hs512",
                "providerId": "hmac-generated",
                "subComponents": {},
                "config": {
                    "kid": [ "f43f7c3d-e27c-4a86-bda1-e1f9fa0b2c0b" ],
                    "secret": [ "FAyCBV9_zIF2oQO0XqgwCJz09iJDMKPHORhWI1ZV4OA9cLFVJCA-z4tEXq2QNU48xDMwv_z_UYIEm73nnJEypuaVwacu6N7jexaKjhqROYidQyPzXAr7QwD6Du1LaLdCAHaBo7rRP3Pl5fDmcX6K3C1Qe1OK86fkZVuD_2TX6No" ],
                    "priority": [ "100" ],
                    "algorithm": [ "HS512" ]
                }
            }
        ]
    },
    "internationalizationEnabled": false,
    "supportedLocales": [],
    "authenticationFlows": [
        {
            "id": "57c6972a-8262-4fdd-9a3d-6454f7e4804d",
            "alias": "Account verification options",
            "description": "Method with which to verity the existing account",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "idp-email-verification",
                    "authenticatorFlow": false,
                    "requirement": "ALTERNATIVE",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "ALTERNATIVE",
                    "priority": 20,
                    "autheticatorFlow": true,
                    "flowAlias": "Verify Existing Account by Re-authentication",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "12219f2a-a63e-4e35-87b0-0c1fc82e9e00",
            "alias": "Browser - Conditional OTP",
            "description": "Flow to determine if the OTP is required for the authentication",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "conditional-user-configured",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "auth-otp-form",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "16a216e0-2305-4a26-8b3c-a1ad95ee0551",
            "alias": "Direct Grant - Conditional OTP",
            "description": "Flow to determine if the OTP is required for the authentication",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "conditional-user-configured",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "direct-grant-validate-otp",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "973a2638-3205-40a5-9ae1-171e862f98c2",
            "alias": "First broker login - Conditional OTP",
            "description": "Flow to determine if the OTP is required for the authentication",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "conditional-user-configured",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "auth-otp-form",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "d024eba2-74d6-4cd2-a149-eda663682a7b",
            "alias": "Handle Existing Account",
            "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "idp-confirm-link",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": true,
                    "flowAlias": "Account verification options",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "37b5496c-9e09-402f-b079-9c588322f91d",
            "alias": "Reset - Conditional OTP",
            "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "conditional-user-configured",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "reset-otp",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "62d51e78-668a-4d31-9369-0611a7507ed5",
            "alias": "User creation or linking",
            "description": "Flow for the existing/non-existing user alternatives",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticatorConfig": "create unique user config",
                    "authenticator": "idp-create-user-if-unique",
                    "authenticatorFlow": false,
                    "requirement": "ALTERNATIVE",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "ALTERNATIVE",
                    "priority": 20,
                    "autheticatorFlow": true,
                    "flowAlias": "Handle Existing Account",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "ab6b9698-fd26-40a7-8edf-fa456a395394",
            "alias": "Verify Existing Account by Re-authentication",
            "description": "Reauthentication of existing account",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "idp-username-password-form",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "CONDITIONAL",
                    "priority": 20,
                    "autheticatorFlow": true,
                    "flowAlias": "First broker login - Conditional OTP",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "74b79e04-bf44-4d84-92e7-04b753801622",
            "alias": "browser",
            "description": "browser based authentication",
            "providerId": "basic-flow",
            "topLevel": true,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "auth-cookie",
                    "authenticatorFlow": false,
                    "requirement": "ALTERNATIVE",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "auth-spnego",
                    "authenticatorFlow": false,
                    "requirement": "DISABLED",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "identity-provider-redirector",
                    "authenticatorFlow": false,
                    "requirement": "ALTERNATIVE",
                    "priority": 25,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "ALTERNATIVE",
                    "priority": 30,
                    "autheticatorFlow": true,
                    "flowAlias": "forms",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "bb15232e-9f1c-4dfd-8d10-e1d35cd1bfde",
            "alias": "clients",
            "description": "Base authentication for clients",
            "providerId": "client-flow",
            "topLevel": true,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "client-secret",
                    "authenticatorFlow": false,
                    "requirement": "ALTERNATIVE",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "client-jwt",
                    "authenticatorFlow": false,
                    "requirement": "ALTERNATIVE",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "client-secret-jwt",
                    "authenticatorFlow": false,
                    "requirement": "ALTERNATIVE",
                    "priority": 30,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "client-x509",
                    "authenticatorFlow": false,
                    "requirement": "ALTERNATIVE",
                    "priority": 40,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "dd3fe894-1594-40fb-949d-9986f36bd725",
            "alias": "direct grant",
            "description": "OpenID Connect Resource Owner Grant",
            "providerId": "basic-flow",
            "topLevel": true,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "direct-grant-validate-username",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "direct-grant-validate-password",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "CONDITIONAL",
                    "priority": 30,
                    "autheticatorFlow": true,
                    "flowAlias": "Direct Grant - Conditional OTP",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "5c277901-3d67-470e-baf1-e47e9ab92dbd",
            "alias": "docker auth",
            "description": "Used by Docker clients to authenticate against the IDP",
            "providerId": "basic-flow",
            "topLevel": true,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "docker-http-basic-authenticator",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "e14e1d45-149f-4090-ad79-7c2d0c2f185c",
            "alias": "first broker login",
            "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
            "providerId": "basic-flow",
            "topLevel": true,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticatorConfig": "review profile config",
                    "authenticator": "idp-review-profile",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": true,
                    "flowAlias": "User creation or linking",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "aa2e5d63-a8e9-43e4-8461-994827de67f5",
            "alias": "forms",
            "description": "Username, password, otp and other auth forms.",
            "providerId": "basic-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "auth-username-password-form",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "CONDITIONAL",
                    "priority": 20,
                    "autheticatorFlow": true,
                    "flowAlias": "Browser - Conditional OTP",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "d88b5042-8af8-4797-9c6a-ae44a9a0fff2",
            "alias": "registration",
            "description": "registration flow",
            "providerId": "basic-flow",
            "topLevel": true,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "registration-page-form",
                    "authenticatorFlow": true,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": true,
                    "flowAlias": "registration form",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "2a9320cb-970e-4b4d-b585-60d2299a043f",
            "alias": "registration form",
            "description": "registration form",
            "providerId": "form-flow",
            "topLevel": false,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "registration-user-creation",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "registration-password-action",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 50,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "registration-recaptcha-action",
                    "authenticatorFlow": false,
                    "requirement": "DISABLED",
                    "priority": 60,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "registration-terms-and-conditions",
                    "authenticatorFlow": false,
                    "requirement": "DISABLED",
                    "priority": 70,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "528ba840-6b22-4c32-ba17-40c99783883e",
            "alias": "reset credentials",
            "description": "Reset credentials for a user if they forgot their password or something",
            "providerId": "basic-flow",
            "topLevel": true,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "reset-credentials-choose-user",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "reset-credential-email",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 20,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticator": "reset-password",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 30,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                },
                {
                    "authenticatorFlow": true,
                    "requirement": "CONDITIONAL",
                    "priority": 40,
                    "autheticatorFlow": true,
                    "flowAlias": "Reset - Conditional OTP",
                    "userSetupAllowed": false
                }
            ]
        },
        {
            "id": "bf172b2d-052a-4ce4-9084-c59e9b82bc10",
            "alias": "saml ecp",
            "description": "SAML ECP Profile Authentication Flow",
            "providerId": "basic-flow",
            "topLevel": true,
            "builtIn": true,
            "authenticationExecutions": [
                {
                    "authenticator": "http-basic-authenticator",
                    "authenticatorFlow": false,
                    "requirement": "REQUIRED",
                    "priority": 10,
                    "autheticatorFlow": false,
                    "userSetupAllowed": false
                }
            ]
        }
    ],
    "authenticatorConfig": [
        {
            "id": "796c70f7-6391-45ed-aafa-4ed82c84d14e",
            "alias": "create unique user config",
            "config": {
                "require.password.update.after.registration": "false"
            }
        },
        {
            "id": "e007b422-2050-43af-b132-10ea16d92f5c",
            "alias": "review profile config",
            "config": {
                "update.profile.on.first.login": "missing"
            }
        }
    ],
    "requiredActions": [
        {
            "alias": "CONFIGURE_TOTP",
            "name": "Configure OTP",
            "providerId": "CONFIGURE_TOTP",
            "enabled": true,
            "defaultAction": false,
            "priority": 10,
            "config": {}
        },
        {
            "alias": "TERMS_AND_CONDITIONS",
            "name": "Terms and Conditions",
            "providerId": "TERMS_AND_CONDITIONS",
            "enabled": false,
            "defaultAction": false,
            "priority": 20,
            "config": {}
        },
        {
            "alias": "UPDATE_PASSWORD",
            "name": "Update Password",
            "providerId": "UPDATE_PASSWORD",
            "enabled": true,
            "defaultAction": false,
            "priority": 30,
            "config": {}
        },
        {
            "alias": "UPDATE_PROFILE",
            "name": "Update Profile",
            "providerId": "UPDATE_PROFILE",
            "enabled": true,
            "defaultAction": false,
            "priority": 40,
            "config": {}
        },
        {
            "alias": "VERIFY_EMAIL",
            "name": "Verify Email",
            "providerId": "VERIFY_EMAIL",
            "enabled": true,
            "defaultAction": false,
            "priority": 50,
            "config": {}
        },
        {
            "alias": "delete_account",
            "name": "Delete Account",
            "providerId": "delete_account",
            "enabled": false,
            "defaultAction": false,
            "priority": 60,
            "config": {}
        },
        {
            "alias": "webauthn-register",
            "name": "Webauthn Register",
            "providerId": "webauthn-register",
            "enabled": true,
            "defaultAction": false,
            "priority": 70,
            "config": {}
        },
        {
            "alias": "webauthn-register-passwordless",
            "name": "Webauthn Register Passwordless",
            "providerId": "webauthn-register-passwordless",
            "enabled": true,
            "defaultAction": false,
            "priority": 80,
            "config": {}
        },
        {
            "alias": "VERIFY_PROFILE",
            "name": "Verify Profile",
            "providerId": "VERIFY_PROFILE",
            "enabled": true,
            "defaultAction": false,
            "priority": 90,
            "config": {}
        },
        {
            "alias": "delete_credential",
            "name": "Delete Credential",
            "providerId": "delete_credential",
            "enabled": true,
            "defaultAction": false,
            "priority": 100,
            "config": {}
        },
        {
            "alias": "update_user_locale",
            "name": "Update User Locale",
            "providerId": "update_user_locale",
            "enabled": true,
            "defaultAction": false,
            "priority": 1000,
            "config": {}
        }
    ],
    "browserFlow": "browser",
    "registrationFlow": "registration",
    "directGrantFlow": "direct grant",
    "resetCredentialsFlow": "reset credentials",
    "clientAuthenticationFlow": "clients",
    "dockerAuthenticationFlow": "docker auth",
    "firstBrokerLoginFlow": "first broker login",
    "attributes": {
        "cibaBackchannelTokenDeliveryMode": "poll",
        "cibaExpiresIn": "120",
        "cibaAuthRequestedUserHint": "login_hint",
        "oauth2DeviceCodeLifespan": "600",
        "clientOfflineSessionMaxLifespan": "0",
        "oauth2DevicePollingInterval": "5",
        "clientSessionIdleTimeout": "0",
        "parRequestUriLifespan": "60",
        "clientSessionMaxLifespan": "0",
        "clientOfflineSessionIdleTimeout": "0",
        "cibaInterval": "5",
        "realmReusableOtpCode": "false"
    },
    "keycloakVersion": "24.0.5",
    "userManagedAccessAllowed": false,
    "clientProfiles": {
        "profiles": []
    },
    "clientPolicies": {
        "policies": []
    }
}

Durchführung von Integrationsgesundheitsprüfungen

Die Keycloak-Hostingintegration unterstützt derzeit keine Integritätsprüfungen und fügt sie auch nicht automatisch hinzu.

Client Integration

Um mit der .NET AspireKeycloakclient-Integration zu beginnen, installieren Sie die 📦Aspire.Keycloak. Die Authentifizierung NuGet-Paket im projekt client-verbrauchend, d. h. das Projekt für die Anwendung, die die Keycloakclientverwendet. Die Keycloakclient Integration registriert JwtBearer- und OpenId Connect-Authentifizierungs-Handler im DI-Container, um eine Verbindung mit einem Keycloakherzustellen.

dotnet add package Aspire.Keycloak.Authentication

JWT-Bearer-Authentifizierung hinzufügen

Rufen Sie in der Program.cs Datei Ihres ASP.NET Core-API-Projekts die AddKeycloakJwtBearer Erweiterungsmethode auf, um die JwtBearer-Authentifizierung mit einem Verbindungsnamen, einem Bereich und allen erforderlichen JWT Bearer-Optionen hinzuzufügen:

builder.Services.AddAuthentication()
                .AddKeycloakJwtBearer(
                    serviceName: "keycloak",
                    realm: "api",
                    options =>
                    {
                        options.Audience = "store.api";
                    });

Sie können viele weitere Optionen über den Action<JwtBearerOptions> configureOptions-Delegat festlegen.

Beispiel für die JWT-Bearer-Authentifizierung

Um die JWT-Bearer-Authentifizierung weiter zu veranschaulichen, siehe das folgende Beispiel:

var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire client integrations.
builder.AddServiceDefaults();

// Add services to the container.
builder.Services.AddProblemDetails();

// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

builder.Services.AddAuthentication()
                .AddKeycloakJwtBearer(
                    serviceName: "keycloak",
                    realm: "WeatherShop",
                    configureOptions: options =>
                {
                    options.RequireHttpsMetadata = false;
                    options.Audience = "weather.api";
                });

builder.Services.AddAuthorizationBuilder();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseExceptionHandler();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

string[] summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];

app.MapGet("/weatherforecast", () =>
    {
        var forecast = Enumerable.Range(1, 5).Select(index =>
            new WeatherForecast
            (
                DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                Random.Shared.Next(-20, 55),
                summaries[Random.Shared.Next(summaries.Length)]
            ))
            .ToArray();
        return forecast;
    })
    .WithName("GetWeatherForecast")
    .RequireAuthorization();

app.MapDefaultEndpoints();

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Die vorangehende ASP.NET Core Minimal-API Program Klasse veranschaulicht:

  • Hinzufügen von Authentifizierungsdiensten zum DI-Container mit der AddAuthentication-API.
  • Hinzufügen der JWT-Bearerauthentifizierung mit der AddKeycloakJwtBearer-API und Konfigurieren:
    • Die serviceName als keycloak.
    • Die realm als WeatherShop.
    • Die options, deren Audience auf weather.api festgelegt ist, und legt RequireHttpsMetadata auf falsefest.
  • Fügt dem DI-Container mit der AddAuthorizationBuilder-API Autorisierungsdienste hinzu.
  • Ruft die RequireAuthorization-API auf, um eine Autorisierung für den /weatherforecast Endpunkt zu erfordern.

Ein vollständiges Arbeitsbeispiel finden Sie unter .NET Aspire Spielfeld: Keycloak Integration.

Hinzufügen der OpenId Connect-Authentifizierung

Rufen Sie in der Program.cs-Datei Ihres API-verbrauchenden Projekts (z. B. Blazor) die AddKeycloakOpenIdConnect Erweiterungsmethode auf, um die OpenId Connect-Authentifizierung hinzuzufügen, indem Sie einen Verbindungsnamen, einen Bereich und alle erforderlichen OpenId Connect-Optionen verwenden:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                .AddKeycloakOpenIdConnect(
                    serviceName: "keycloak",
                    realm: "api",
                    options =>
                    {
                        options.ClientId = "StoreWeb";
                        options.ResponseType = OpenIdConnectResponseType.Code;
                        options.Scope.Add("store:all");
                    });

Sie können viele weitere Optionen über den Action<OpenIdConnectOptions>? configureOptions-Delegat festlegen.

OpenId Connect-Authentifizierung (Beispiel)

Um die OpenId Connect-Authentifizierung weiter zu veranschaulichen, betrachten Sie das folgende Beispiel:

using System.IdentityModel.Tokens.Jwt;

using AspireApp.Web;
using AspireApp.Web.Components;

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire client integrations.
builder.AddServiceDefaults();

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddOutputCache();

builder.Services.AddHttpContextAccessor()
                .AddTransient<AuthorizationHandler>();

builder.Services.AddHttpClient<WeatherApiClient>(client =>
    {
        // This URL uses "https+http://" to indicate HTTPS is preferred over HTTP.
        // Learn more about service discovery scheme resolution at https://aka.ms/dotnet/sdschemes.
        client.BaseAddress = new("https+http://apiservice");
    })
    .AddHttpMessageHandler<AuthorizationHandler>();

var oidcScheme = OpenIdConnectDefaults.AuthenticationScheme;

builder.Services.AddAuthentication(oidcScheme)
                .AddKeycloakOpenIdConnect("keycloak", realm: "WeatherShop", oidcScheme, options =>
                {
                    options.ClientId = "WeatherWeb";
                    options.ResponseType = OpenIdConnectResponseType.Code;
                    options.Scope.Add("weather:all");
                    options.RequireHttpsMetadata = false;
                    options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
                    options.SaveTokens = true;
                    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                })
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

builder.Services.AddCascadingAuthenticationState();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseAntiforgery();

app.UseOutputCache();

app.MapStaticAssets();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.MapDefaultEndpoints();
app.MapLoginAndLogout();

app.Run();

Die vorangehende ASP.NET CoreBlazorProgram Klasse:

Der letzte Hinweis ist die Erweiterungsmethode MapLoginAndLogout, die Anmelde- und Abmelderouten zur App Blazor hinzufügt. Dies ist wie folgt definiert:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.HttpResults;

namespace AspireApp.Web;

internal static class LoginLogoutEndpointRouteBuilderExtensions
{
    internal static IEndpointConventionBuilder MapLoginAndLogout(
        this IEndpointRouteBuilder endpoints)
    {
        var group = endpoints.MapGroup("authentication");

        group.MapGet(pattern: "/login", OnLogin).AllowAnonymous();
        group.MapPost(pattern: "/logout", OnLogout);

        return group;
    }

    static ChallengeHttpResult OnLogin() =>
        TypedResults.Challenge(properties: new AuthenticationProperties
        {
            RedirectUri = "/"
        });

    static SignOutHttpResult OnLogout() =>
        TypedResults.SignOut(properties: new AuthenticationProperties
        {
            RedirectUri = "/"
        },
        [
            CookieAuthenticationDefaults.AuthenticationScheme,
            OpenIdConnectDefaults.AuthenticationScheme
        ]);
}

Der vorangehende Code:

  • Ordnet eine Gruppe der Route authentication zu und ordnet zwei Endpunkte den Routen login und logout zu.
    • Ordnet eine GET-Anforderung der /login-Route zu, deren Handler die OnLogin-Methode ist – dies ist ein anonymer Endpunkt.
    • Ordnet eine GET-Anforderung der /logout-Route zu, deren Handler die OnLogout-Methode ist.

Die AuthorizationHandler ist ein benutzerdefinierter Handler, der das Bearer-Token zur HttpClient-Anforderung hinzufügt. Der Handler ist wie folgt definiert:

using Microsoft.AspNetCore.Authentication;
using System.Net.Http.Headers;

namespace AspireApp.Web;

public class AuthorizationHandler(IHttpContextAccessor httpContextAccessor)
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var httpContext = httpContextAccessor.HttpContext ??
            throw new InvalidOperationException("""
                No HttpContext available from the IHttpContextAccessor.
                """);

        var accessToken = await httpContext.GetTokenAsync("access_token");

        if (!string.IsNullOrWhiteSpace(accessToken))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

Der vorangehende Code:

  • Ist eine Unterklasse der Klasse DelegatingHandler.
  • Fügt den IHttpContextAccessor Dienst in den primären Konstruktor ein.
  • Überschreibt die SendAsync-Methode, um das Bearer-Token zur HttpClient-Anforderung hinzuzufügen.
    • Die access_token wird aus dem HttpContext abgerufen und zur Kopfzeile Authorization hinzugefügt.

Um den Authentifizierungsfluss zu visualisieren, ziehen Sie das folgende Sequenzdiagramm in Betracht:

Authentifizierungsflussdiagramm– veranschaulicht eine Benutzeranforderung für ein Zugriffstoken, Keycloak das Zurückgeben eines JWT und das Token, das an die API weitergeleitet wird.

Ein vollständiges Arbeitsbeispiel finden Sie unter .NET Aspire-Spielplatz: Keycloak-Integration.

Siehe auch