Compartilhar via


integração .NET AspireKeycloak (versão prévia)

Inclui:integração de hospedagem e integração Client

Keycloak é uma solução de Gerenciamento de Acesso e Identidade de software livre voltada para aplicativos e serviços modernos. A integração .NET AspireKeycloak permite que você se conecte a instâncias de Keycloak existentes ou crie novas instâncias de .NET com a imagem de contêiner quay.io/keycloak/keycloak.

Integração de hospedagem

Os modelos de integração de hospedagem do .NET AspireKeycloak modelam o server como tipo de KeycloakResource. Para acessar esses tipos e APIs, adicione o 📦Aspire.Hosting.Keycloak pacote NuGet no projeto de host de aplicativo .

dotnet add package Aspire.Hosting.Keycloak --prerelease

Para obter mais informações, consulte dotnet add package ou Gerenciar dependências de pacotes em aplicações .NET.

Adicionar Keycloak recurso

No projeto host do seu aplicativo, chame AddKeycloak para adicionar e retornar um construtor de recursos do tipo Keycloak. Encadear uma chamada ao construtor de recursos retornado para configurar o 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...

Dica

Para desenvolvimento local, use uma porta estável para o recurso de Keycloak (8080 no exemplo anterior). Pode ser qualquer porta, mas deve ser estável para evitar problemas com cookies de navegador que persistirão tokens OIDC (que incluem a URL da autoridade, com porta) além do tempo de vida do host do aplicativo .

Quando .NET.NET Aspire adiciona uma imagem de contêiner ao host do aplicativo, conforme mostrado no exemplo anterior com a imagem quay.io/keycloak/keycloak, ele cria uma nova instância de Keycloak em seu computador local. O recurso Keycloak inclui credenciais padrão:

Quando o host do aplicativo é executado, a senha é armazenada no repositório secreto do host do aplicativo. Ele é adicionado à seção Parameters, por exemplo:

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

O nome do parâmetro é keycloak-password, mas na verdade é apenas formatar o nome do recurso com um sufixo -password. Para obter mais informações, consulte Armazenamento seguro de segredos do aplicativo no desenvolvimento, em ASP.NET Core e Adicionar recurso Keycloak.

O método WithReference configura uma conexão no ExampleProject chamado keycloak e o WaitFor instrui o host do aplicativo a não iniciar o serviço dependente até que o recurso de keycloak esteja pronto.

Dica

Se você preferir se conectar a uma instância de Keycloak existente, chame AddConnectionString em vez disso. Para obter mais informações, consulte Consulte recursos existentes.

Adicionar o recurso Keycloak, com volume de dados

Para adicionar um volume de dados ao recurso Keycloak, chame o método WithDataVolume no recurso 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...

O volume de dados é usado para persistir os dados associados a Keycloak fora do ciclo de vida de seu contêiner. O volume de dados é montado no caminho /opt/keycloak/data no contêiner Keycloak e, quando um parâmetro name não é fornecido, o nome é gerado aleatoriamente. Para obter mais informações sobre volumes de dados e detalhes sobre por que eles são preferenciais em vez de associar montagens, consulte Docker documentos: Volumes.

Aviso

As credenciais de administrador são armazenadas no volume de dados. Ao usar um volume de dados e se as credenciais forem alteradas, ele não funcionará até que você exclua o volume.

Adicionar Keycloak recurso com a montagem de associação de dados

Para adicionar uma montagem de associação de dados ao recurso Keycloak, chame o método 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...

Importante

Os de dados associam montagens têm funcionalidade limitada em comparação com volumes, que oferecem melhor desempenho, portabilidade e segurança, tornando-os mais adequados para ambientes de produção. No entanto, as montagens vinculadas permitem acesso direto e modificação de arquivos no sistema host, ideal para desenvolvimento e teste nos quais alterações em tempo real são necessárias.

As montagens de vinculação de dados dependem do sistema de arquivos da máquina host para persistir os dados Keycloak entre reinicializações de contêiner. A montagem de dados é feita no caminho C:\Keycloak\Data no Windows (ou /Keycloak/Data no Unix) no contêiner Keycloak no computador host. Para obter mais informações sobre montagens de associação de dados, consulte Docker documentos: Associar montagens.

Adicionar Keycloak recurso com parâmetros

Quando você quiser fornecer explicitamente o nome de usuário e a senha do administrador usados pela imagem do contêiner, você pode fornecer essas credenciais como parâmetros. Considere o seguinte exemplo alternativo:

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

Os parâmetros username e password geralmente são fornecidos como variáveis de ambiente ou segredos. Os parâmetros são usados para definir as variáveis de ambiente KEYCLOAK_ADMIN e KEYCLOAK_ADMIN_PASSWORD no contêiner. Para obter mais informações sobre como fornecer parâmetros, consulte Parâmetros externos.

Adicionar recurso Keycloak com importação de realm

Para importar um realm para Keycloak, chame o método 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();

Os arquivos de importação do realm são montados em /opt/keycloak/data/import no contêiner Keycloak. Arquivos de importação do realm são arquivos JSON que representam a configuração do realm. Para obter mais informações sobre a importação de domínio, consulte os documentos Keycloak: Importando um domínio.

Por exemplo, o seguinte arquivo JSON pode ser adicionado ao projeto de host do aplicativo em uma pasta /Realms para servir como um arquivo de configuração de realm de origem:


exemplo de alternância de domínio 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": []
    }
}

Verificações de integridade de integração de hospedagem

A integração de hospedagem do Keycloak atualmente não dá suporte a uma verificação de integridade e também não as adiciona automaticamente.

integração Client

Para começar a usar a integração .NET AspireKeycloakclient, instale o pacote NuGet 📦Aspire.Keycloak.Autenticação no projeto consumidor de client, ou seja, o projeto do aplicativo que usa o Keycloakclient. A integração Keycloakclient registra manipuladores de autenticação JwtBearer e OpenId Connect no contêiner de DI para se conectar a um Keycloak.

dotnet add package Aspire.Keycloak.Authentication

Adicionar autenticação de portador JWT

No arquivo Program.cs do seu projeto de API ASP.NET Core, chame o método de extensão AddKeycloakJwtBearer para adicionar a autenticação JwtBearer, usando um nome de conexão, um domínio e quaisquer opções necessárias do JwtBearer:

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

Você pode definir muitas outras opções por meio do delegado Action<JwtBearerOptions> configureOptions.

Exemplo de autenticação do portador JWT

Para exemplificar ainda mais a autenticação do portador JWT, considere o seguinte exemplo:

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

A classe de Program de API Mínima ASP.NET Core anterior demonstra:

  • Adicionando serviços de autenticação ao contêiner de DI com a API de AddAuthentication.
  • Adicionando a autenticação do portador JWT com a API AddKeycloakJwtBearer e configurando:
    • serviceName é o equivalente de keycloak.
    • O realm como WeatherShop.
    • O options com o Audience definido como weather.api e define RequireHttpsMetadata como false.
  • Adiciona serviços de autorização ao contêiner de DI com a API de AddAuthorizationBuilder.
  • Chama a API RequireAuthorization para solicitar autorização no endpoint /weatherforecast.

Para um exemplo completo de funcionamento, consulte .NET Aspire playground: integração Keycloak.

Adicionar autenticação do OpenId Connect

No arquivo Program.cs do projeto que consome API (por exemplo, Blazor), chame o método de extensão AddKeycloakOpenIdConnect para adicionar a autenticação OpenId Connect, usando um nome de conexão, um realm, e todas as opções necessárias do OpenId Connect:

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

Você pode definir muitas outras opções por meio do delegado Action<OpenIdConnectOptions>? configureOptions.

Exemplo de autenticação do OpenId Connect

Para exemplificar ainda mais a autenticação do OpenId Connect, considere o seguinte exemplo:

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

A classe ASP.NET CoreBlazorProgram anterior:

A última observação é o método de extensão MapLoginAndLogout que adiciona rotas de login e logoff ao aplicativo Blazor. Isso é definido da seguinte maneira:

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

O código anterior:

  • Mapeia um grupo para a rota authentication e dois endpoints para as rotas login e logout.
    • Mapeia uma solicitação GET para a rota /login, cujo manipulador é o método OnLogin — este é um endpoint anônimo.
    • Mapeia uma solicitação GET para a rota /logout, cujo manipulador é o método OnLogout.

O AuthorizationHandler é um manipulador personalizado que adiciona o token Bearer à solicitação HttpClient. O manipulador é definido da seguinte maneira:

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

O código anterior:

  • É uma subclasse da classe DelegatingHandler.
  • Injeta o serviço IHttpContextAccessor no construtor primário.
  • Substitui o método SendAsync para adicionar o token Bearer à solicitação HttpClient:
    • O access_token é recuperado do HttpContext e adicionado ao cabeçalho Authorization.

Para ajudar a visualizar o fluxo de autenticação, considere o seguinte diagrama de sequência:

diagrama de fluxo de autenticação — demonstrando uma solicitação de usuário para um token de acesso, Keycloak retornando um JWT e o token sendo encaminhado para a API.

Para obter um exemplo de trabalho completo, consulte .NET Aspire playground: integração Keycloak.

Consulte também