Partager via


intégration de .NET AspireKeycloak (préversion)

inclut :intégration d'hébergement et intégration Client

Keycloak est une solution open source Identity and Access Management destinée aux applications et services modernes. L’intégration .NET AspireKeycloak vous permet de vous connecter à des instances de Keycloak existantes ou de créer de nouvelles instances à partir de .NET avec l’image conteneur quay.io/keycloak/keycloak.

Intégration de l’hébergement

Le .NET AspireKeycloak hébergeant les modèles d’intégration de l'server comme type de KeycloakResource. Pour accéder à ces types et API, ajoutez le package NuGet 📦Aspire.Hosting.Keycloak dans le projet hôte de l'application .

dotnet add package Aspire.Hosting.Keycloak --prerelease

Pour plus d’informations, consultez dotnet add package ou Gérer les dépendances des packages dans les applications .NET.

Ajouter Keycloak ressource

Dans votre projet hôte d’application, appelez AddKeycloak pour ajouter et retourner un générateur de ressources Keycloak. Chaînez un appel au générateur de ressources retourné pour configurer le Keycloak.

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...

Pourboire

Pour le développement local, utilisez un port stable pour la ressource Keycloak (8080 dans l’exemple précédent). Il peut s’agir de n’importe quel port, mais il doit être stable pour éviter les problèmes liés aux cookies de navigateur qui conservent les jetons OIDC (qui incluent l’URL de l’autorité, avec le port) au-delà de la durée de vie de l’hôte d’application .

Lorsque .NET.NET Aspire ajoute une image conteneur à l’hôte de l’application, comme illustré dans l’exemple précédent avec l’image quay.io/keycloak/keycloak, il crée une instance de Keycloak sur votre ordinateur local. La ressource Keycloak inclut les informations d’identification par défaut :

Lorsque l’hôte de l’application s’exécute, le mot de passe est stocké dans le stockage secret de l’hôte d’application. Elle est ajoutée à la section Parameters, par exemple :

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

Le nom du paramètre est keycloak-password, mais il suffit de mettre en forme le nom de la ressource avec un suffixe -password. Pour plus d'informations, consultez Stockage sécurisé des secrets d'application en développement dans ASP.NET Core et ajoutez la ressourceKeycloak.

La méthode WithReference configure une connexion dans le ExampleProject nommé keycloak et l'WaitFor indique à l’hôte de l’application de ne pas démarrer le service dépendant tant que la ressource keycloak n’est pas prête.

Pourboire

Si vous préférez vous connecter à une instance de Keycloak existante, appelez AddConnectionString à la place. Pour plus d’informations, consultez Référencer les ressources existantes.

Ajouter la ressource Keycloak, avec le volume de données

Pour ajouter un volume de données à la ressource Keycloak, appelez la méthode WithDataVolume sur la ressource Keycloak :

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...

Le volume de données est utilisé pour conserver les données Keycloak en dehors du cycle de vie de son conteneur. Le volume de données est monté dans le conteneur Keycloak, au chemin d'accès /opt/keycloak/data, et lorsqu'un paramètre name n'est pas fourni, le nom est généré de manière aléatoire. Pour plus d’informations sur les volumes de données et sur la raison pour laquelle ils sont préférés par rapport aux montages de liaison , consultez la documentation Docker : Volumes.

Avertissement

Les informations d’identification de l’administrateur sont stockées dans le volume de données. Lorsque vous utilisez un volume de données et si les informations d’identification changent, cela ne fonctionnera pas tant que vous ne supprimez pas le volume.

Ajouter Keycloak ressource avec montage de liaison de données

Pour ajouter un montage de liaison de données à la ressource Keycloak, appelez la méthode WithDataBindMount :

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...

Important

Les montages de liaison de données ont des fonctionnalités limitées par rapport aux volumes , qui offrent de meilleures performances, ainsi que la portabilité et la sécurité, les rendant ainsi plus adaptés aux environnements de production. Toutefois, les montages de liaison autorisent l’accès direct et la modification des fichiers sur le système hôte, ce qui est idéal pour le développement et le test où des modifications en temps réel sont nécessaires.

Les montages de liaison de données s’appuient sur le système de fichiers de l’ordinateur hôte pour conserver les données Keycloak entre les redémarrages de conteneur. Le montage de liaison de données est monté sur le chemin d’accès C:\Keycloak\Data sur Windows (ou /Keycloak/Data sur Unix) sur l’ordinateur hôte dans le conteneur Keycloak. Pour plus d’informations sur les montages de liaison de données, consultez Docker docs : Liaison de montages.

Ajouter Keycloak ressource avec des paramètres

Lorsque vous souhaitez fournir explicitement le nom d’utilisateur administrateur et le mot de passe utilisés par l’image conteneur, vous pouvez fournir ces informations d’identification en tant que paramètres. Prenons l’exemple de remplacement suivant :

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...

Les paramètres username et password sont généralement fournis en tant que variables d’environnement ou secrets. Les paramètres sont utilisés pour définir les variables d’environnement KEYCLOAK_ADMIN et KEYCLOAK_ADMIN_PASSWORD dans le conteneur. Pour plus d’informations sur la fourniture de paramètres, consultez paramètres externes.

Ajouter Keycloak ressource avec l’importation de domaine

Pour importer un domaine dans Keycloak, appelez la méthode WithRealmImport :

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();

Les fichiers d’importation de domaine sont montés sur /opt/keycloak/data/import dans le conteneur Keycloak. Les fichiers d’importation d'espace sont des fichiers JSON qui représentent la configuration de l'espace. Pour plus d’informations sur l’importation de domaine, consultez Keycloak docs : Importation d’un domaine.

Par exemple, le fichier de JSON suivant peut être ajouté au projet hôte d’application dans un dossier /Realms pour servir de fichier de configuration de domaine source :


Exemple de bascule de domaine 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": []
    }
}

Vérifications de l'intégrité de l’intégration d’hébergement

L’intégration d’hébergement Keycloak ne prend actuellement pas en charge les vérifications d’intégrité, ni ne les ajoute automatiquement.

intégration de Client

Pour commencer à utiliser l’intégration .NET AspireKeycloakclient, installez le paquet NuGet 📦Aspire.Keycloak.Authentification dans le projet consommateur client, c’est-à-dire le projet pour l’application qui utilise le Keycloakclient. L'intégration Keycloakclient inscrit les gestionnaires d'authentification JwtBearer et OpenId Connect dans le conteneur DI pour la connexion à un Keycloak.

dotnet add package Aspire.Keycloak.Authentication

Ajouter l’authentification du porteur JWT

Dans le fichier Program.cs de votre projet d’API ASP.NET Core, appelez la méthode d’extension AddKeycloakJwtBearer pour ajouter l’authentification JwtBearer, à l’aide d’un nom de connexion, d’un domaine et de toutes les options de porteur JWT requises :

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

Vous pouvez définir de nombreuses autres options via le délégué Action<JwtBearerOptions> configureOptions.

Exemple d’authentification du porteur JWT

Pour illustrer davantage l’authentification du porteur JWT, considérez l’exemple suivant :

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);
}

La classe ASP.NET Core Minimal API Program précédente illustre :

  • Ajout de services d’authentification au conteneur DI avec l’API AddAuthentication.
  • Ajout de l’authentification du porteur JWT avec l’API AddKeycloakJwtBearer et configuration :
    • Le serviceName en tant que keycloak.
    • Le realm en tant que WeatherShop.
    • Le options avec le Audience défini sur weather.api, puis définit RequireHttpsMetadata sur false.
  • Ajoute des services d’autorisation au conteneur DI avec l’API AddAuthorizationBuilder.
  • Appelle l’API RequireAuthorization pour exiger une autorisation sur le point de terminaison /weatherforecast.

Pour un exemple de fonctionnement complet, consultez .NET Aspire boîte de jeu : intégration Keycloak.

Ajouter l’authentification OpenId Connect

Dans le fichier Program.cs de votre projet consommant l’API (par exemple, Blazor), appelez la méthode d’extension AddKeycloakOpenIdConnect pour ajouter l’authentification OpenId Connect, à l’aide d’un nom de connexion, d’un domaine et de toutes les options OpenId Connect requises :

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

Vous pouvez définir de nombreuses autres options via le délégué Action<OpenIdConnectOptions>? configureOptions.

Exemple d’authentification OpenId Connect

Pour illustrer davantage l’authentification OpenId Connect, considérez l’exemple suivant :

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();

Classe ASP.NET CoreBlazorProgram précédente :

Le dernier appel concerne la méthode d’extension MapLoginAndLogout qui ajoute des routes de connexion et de déconnexion à l’application Blazor. Ceci est défini comme suit :

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
        ]);
}

Code précédent :

  • Mappe un groupe pour l’itinéraire authentication et mappe deux points de terminaison pour les itinéraires login et logout :
    • Mappe une requête GET à l’itinéraire /login dont le gestionnaire est la méthode OnLogin ; il s’agit d’un point de terminaison anonyme.
    • Mappe une requête GET à l’itinéraire /logout dont le gestionnaire est la méthode OnLogout.

Le AuthorizationHandler est un gestionnaire personnalisé qui ajoute le jeton Bearer à la requête HttpClient. Le gestionnaire est défini comme suit :

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);
    }
}

Code précédent :

  • Sous-classe de la classe DelegatingHandler.
  • Injecte le service IHttpContextAccessor dans le constructeur principal.
  • Remplace la méthode SendAsync pour ajouter le jeton Bearer à la demande de HttpClient :
    • Le access_token est récupéré à partir du HttpContext et ajouté à l’en-tête Authorization.

Pour vous aider à visualiser le flux d’authentification, tenez compte du diagramme de séquence suivant :

diagramme de flux d’authentification , illustrant une demande d’utilisateur pour un jeton d’accès, Keycloak renvoyer un JWT et le jeton transféré à l’API.

Pour obtenir un exemple complet de travail, consultez .NET Aspire terrain de jeu : Keycloakd’intégration .

Voir aussi