```
------------------
### Testing & development
#### Build the project
```shell
$ mvn package
```
-
-#### Run Keycloak with authenticator in docker-compose
-After building a project, do following to start Keycloak with bundled authenticator jar and dummy configuration ([`dev-realm.json`](src/test/resources/dev-realm.json)).
-```shell
-$ docker-compose up --build
-```
-Open browser and go to http://localhost:8081/auth/realms/dev-realm/account
-use _Username or email_ = `test`, _Password_ = `test` and _Foot size_ = `46` to login.
-
-##### Debug in docker with IntelliJ
-`.github/debug-in-docker.run.xml`
-
-#### Automation tests
-##### Build test docker image
+#### Run Keycloak with authenticator in docker compose
+After building a project, do following to start Keycloak with bundled authenticator jar and dummy configuration ([`dev-realm.json`](dev-realm.json)).
```shell
-$ docker-compose build
+$ docker compose up
```
-
-##### Running tests with chrome
-```shell
-$ mvn test -P automation-tests
-```
-
-##### Running tests in docker
-```shell
-$ mvn test -P automation-tests -D selenide.headless=true
-```
\ No newline at end of file
+Open browser and go to http://localhost:8080/realms/dev-realm/account
+use _Username or email_ = `test`, _Password_ = `test` and _Favorite number_ = `46` to login.
diff --git a/dev-realm.json b/dev-realm.json
new file mode 100644
index 0000000..4f75efb
--- /dev/null
+++ b/dev-realm.json
@@ -0,0 +1,1882 @@
+{
+ "id": "834073ca-9a3c-4e36-aa47-f8866b247935",
+ "realm": "dev-realm",
+ "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": false,
+ "registrationEmailAsUsername": false,
+ "rememberMe": false,
+ "verifyEmail": false,
+ "loginWithEmailAllowed": true,
+ "duplicateEmailsAllowed": false,
+ "resetPasswordAllowed": false,
+ "editUsernameAllowed": false,
+ "bruteForceProtected": false,
+ "permanentLockout": false,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
+ "defaultRole": {
+ "id": "2fe40b1b-b89a-402a-8ce6-566a78268bc4",
+ "name": "default-roles-dev-realm",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "clientRole": false,
+ "containerId": "834073ca-9a3c-4e36-aa47-f8866b247935"
+ },
+ "requiredCredentials": [
+ "password"
+ ],
+ "otpPolicyType": "totp",
+ "otpPolicyAlgorithm": "HmacSHA1",
+ "otpPolicyInitialCounter": 0,
+ "otpPolicyDigits": 6,
+ "otpPolicyLookAheadWindow": 1,
+ "otpPolicyPeriod": 30,
+ "otpPolicyCodeReusable": false,
+ "otpSupportedApplications": [
+ "totpAppMicrosoftAuthenticatorName",
+ "totpAppFreeOTPName",
+ "totpAppGoogleName"
+ ],
+ "webAuthnPolicyRpEntityName": "keycloak",
+ "webAuthnPolicySignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyRpId": "",
+ "webAuthnPolicyAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyRequireResidentKey": "not specified",
+ "webAuthnPolicyUserVerificationRequirement": "not specified",
+ "webAuthnPolicyCreateTimeout": 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyAcceptableAaguids": [],
+ "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyPasswordlessRpId": "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout": 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids": [],
+ "scopeMappings": [
+ {
+ "clientScope": "offline_access",
+ "roles": [
+ "offline_access"
+ ]
+ }
+ ],
+ "clientScopeMappings": {
+ "account": [
+ {
+ "client": "account-console",
+ "roles": [
+ "manage-account",
+ "view-groups"
+ ]
+ }
+ ]
+ },
+ "clients": [
+ {
+ "id": "6c84289f-d0ea-4651-b6a9-dc9cb06ce9a8",
+ "clientId": "account",
+ "name": "${client_account}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/dev-realm/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/dev-realm/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",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "1a60274d-756d-4351-8c5e-a41769834c49",
+ "clientId": "account-console",
+ "name": "${client_account-console}",
+ "description": "",
+ "rootUrl": "${authBaseUrl}",
+ "adminUrl": "",
+ "baseUrl": "/realms/dev-realm/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/dev-realm/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "login_theme": "base-with-attribute",
+ "post.logout.redirect.uris": "+",
+ "oauth2.device.authorization.grant.enabled": "false",
+ "backchannel.logout.revoke.offline.tokens": "false",
+ "use.refresh.tokens": "true",
+ "oidc.ciba.grant.enabled": "false",
+ "backchannel.logout.session.required": "true",
+ "client_credentials.use_refresh_token": "false",
+ "tls.client.certificate.bound.access.tokens": "false",
+ "require.pushed.authorization.requests": "false",
+ "acr.loa.map": "{}",
+ "display.on.consent.screen": "false",
+ "pkce.code.challenge.method": "S256",
+ "token.response.type.bearer.lower-case": "false"
+ },
+ "authenticationFlowBindingOverrides": {
+ "browser": "f67cafc8-d118-4824-9cc5-a704f6e53d32"
+ },
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "c248feec-49a5-49ab-b2c1-c513cc55731c",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "ca55cdf2-5e24-473d-9ed3-7d3415638d65",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "a52ee408-0952-4913-b572-d8a343e1684b",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "4db9b7d3-fb96-4b14-a8cf-b2f75525f5c1",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "6037cb4f-e522-4916-b2ef-18bbe37398cf",
+ "clientId": "security-admin-console",
+ "name": "${client_security-admin-console}",
+ "rootUrl": "${authAdminUrl}",
+ "baseUrl": "/admin/dev-realm/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/admin/dev-realm/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": "22ba440a-238f-4786-9f14-d0243b093365",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "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",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ }
+ ],
+ "clientScopes": [
+ {
+ "id": "e1340e1d-18db-4b28-8d95-01b0efe85648",
+ "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": "731ebd0c-60fd-4334-82f5-c6b655bea46a",
+ "name": "email verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "emailVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email_verified",
+ "jsonType.label": "boolean"
+ }
+ },
+ {
+ "id": "8fff12dd-e855-4bdb-96c5-fa22d1f3c4b1",
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "fa673ddd-a855-4590-8d4f-ab13f7e247af",
+ "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": "f7e82b99-268a-4c4c-a7eb-bb62e578081d",
+ "name": "acr loa level",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-acr-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true"
+ }
+ }
+ ]
+ },
+ {
+ "id": "dd2cc4ab-9ad3-40f5-b874-6e3c9e0caefa",
+ "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": "7df1b49b-7640-4e1e-85b6-370557dc7b16",
+ "name": "address",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-address-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute.formatted": "formatted",
+ "user.attribute.country": "country",
+ "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": "5419107b-28fd-442a-a4da-42f3a6ffdc54",
+ "name": "role_list",
+ "description": "SAML role list",
+ "protocol": "saml",
+ "attributes": {
+ "consent.screen.text": "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "5fe1d7c5-25c4-49a0-8821-ba0408f17570",
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "id": "27824e4d-de85-48d1-a942-d427a7c6584b",
+ "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": "dccadfa1-3772-492f-b365-1992efc50273",
+ "name": "allowed web origins",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-allowed-origins-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ]
+ },
+ {
+ "id": "c82c6fcb-a954-4f73-8fea-9c882f26f8a1",
+ "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": "5d2bc3ff-cb61-4df7-bef3-542fc524e84e",
+ "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": "066eab59-34cb-466d-84ef-ace79cf38d53",
+ "name": "phone number",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumber",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "c56590c7-8045-4f64-83d3-13eee3067892",
+ "name": "phone number verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumberVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ },
+ {
+ "id": "a4d1d6ec-51ca-4bf1-b15b-53ad31b968d3",
+ "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": "94f73528-55ee-42e4-b62d-665ea7d8f637",
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ },
+ {
+ "id": "64d400e9-4ca9-48c2-b3b7-98048be0ca08",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ },
+ {
+ "id": "869f0374-0349-43c8-82a1-7fccb90e0027",
+ "name": "client roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-client-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "resource_access.${client_id}.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ }
+ ]
+ },
+ {
+ "id": "c1e4540f-6360-4f6f-8cf5-f227c82e2347",
+ "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": "f65c9ad2-d1dd-4e4a-befb-10805f26f331",
+ "name": "groups",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "user.attribute": "foo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "dbc22bba-3e86-4558-9179-06929f870065",
+ "name": "upn",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "upn",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "e31c4c42-d263-44ad-b999-41578169c0dc",
+ "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": "68c442cd-ad18-46f9-9d1b-5a58606b6bdf",
+ "name": "profile",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "profile",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "profile",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "4e60bf75-e2fb-43ed-ad9c-a90e4a58f6a0",
+ "name": "website",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "website",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "website",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "47a09725-3bf9-427a-8196-adfe4e3d3551",
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ },
+ {
+ "id": "9a6f2d9c-9ead-488b-91db-8766e4e7d7d0",
+ "name": "middle name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "middleName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "middle_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "e2092e09-72a9-4b6d-8a5d-d81b24524d04",
+ "name": "gender",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "gender",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "gender",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "01814e18-7d30-4420-9efe-dd7b403e6e8c",
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "9fff3b34-cee7-400c-9282-507262a9ac0e",
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "c668358d-6b1b-433e-a1fe-1da138aac267",
+ "name": "birthdate",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "birthdate",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "birthdate",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "0e5d8488-9799-4370-8176-0b7c6883f408",
+ "name": "picture",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "picture",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "picture",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "912efed6-058b-4873-a7e0-b051976f0610",
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "4237436e-cab0-4e23-8cf3-197809517d29",
+ "name": "zoneinfo",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "zoneinfo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "zoneinfo",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "763004da-39cd-4fe1-a739-027366e5c24e",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "fdc6c54b-8b7a-496d-9292-14cf8a24aa66",
+ "name": "nickname",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "nickname",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "nickname",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "0bab3185-3fe0-4efe-b29f-26acebe4b08a",
+ "name": "updated at",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "updatedAt",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "updated_at",
+ "jsonType.label": "long"
+ }
+ }
+ ]
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "role_list",
+ "profile",
+ "email",
+ "roles",
+ "web-origins",
+ "acr"
+ ],
+ "defaultOptionalClientScopes": [
+ "offline_access",
+ "address",
+ "phone",
+ "microprofile-jwt"
+ ],
+ "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": "48899fd6-2798-4155-87dd-7ac2c6c2f72f",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "77dfaff3-bc24-489a-8485-c9528dca999c",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "saml-role-list-mapper",
+ "saml-user-property-mapper",
+ "oidc-full-name-mapper",
+ "oidc-address-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-user-attribute-mapper"
+ ]
+ }
+ },
+ {
+ "id": "ba87f7eb-9c2f-4f75-8f8c-db11fdb74eaa",
+ "name": "Full Scope Disabled",
+ "providerId": "scope",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "981d1165-bd59-421e-9589-b6dc81d6f87b",
+ "name": "Consent Required",
+ "providerId": "consent-required",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "1d3f4d16-ad48-4de9-8957-574575a487b6",
+ "name": "Max Clients Limit",
+ "providerId": "max-clients",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "max-clients": [
+ "200"
+ ]
+ }
+ },
+ {
+ "id": "ffb888eb-e9d0-4549-9c15-a9cfe5ee8ce3",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "47984ad9-3db9-4d8d-8c99-e8569bfb113c",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "saml-user-attribute-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-role-list-mapper",
+ "oidc-full-name-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "oidc-address-mapper",
+ "saml-user-property-mapper"
+ ]
+ }
+ },
+ {
+ "id": "8e240cdb-a355-4fa5-91c4-7ec5cd515d69",
+ "name": "Trusted Hosts",
+ "providerId": "trusted-hosts",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "host-sending-registration-request-must-match": [
+ "true"
+ ],
+ "client-uris-must-match": [
+ "true"
+ ]
+ }
+ }
+ ],
+ "org.keycloak.keys.KeyProvider": [
+ {
+ "id": "0d10601a-b4d4-4583-bd68-225207abc244",
+ "name": "hmac-generated",
+ "providerId": "hmac-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "HS256"
+ ]
+ }
+ },
+ {
+ "id": "3cb34b93-0d5f-48c3-89f6-23c6db611073",
+ "name": "aes-generated",
+ "providerId": "aes-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "id": "d708af13-78e3-4ca8-960f-dee9b13ad8f1",
+ "name": "rsa-enc-generated",
+ "providerId": "rsa-enc-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "RSA-OAEP"
+ ]
+ }
+ },
+ {
+ "id": "96aeefca-ccb4-40f5-bdae-6c21c41f64c0",
+ "name": "rsa-generated",
+ "providerId": "rsa-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ }
+ ]
+ },
+ "internationalizationEnabled": false,
+ "supportedLocales": [],
+ "authenticationFlows": [
+ {
+ "id": "88635639-b59c-4f3f-b8d8-49a67faa2a85",
+ "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": "c2c32425-5fdc-418f-bcf2-49c8e4723bb1",
+ "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": "f67cafc8-d118-4824-9cc5-a704f6e53d32",
+ "alias": "Browser with user attribute",
+ "description": "browser based authentication",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": false,
+ "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": "Browser with user attribute forms",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "id": "4810d943-bba2-4796-8883-b4347fcd5fe9",
+ "alias": "Browser with user attribute Browser - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": false,
+ "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": "be489eea-ff3b-4970-bd95-e01edfdcf6ca",
+ "alias": "Browser with user attribute forms",
+ "description": "Username, password, otp and other auth forms.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": false,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "username password favorite number form config",
+ "authenticator": "auth-username-password-attr-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 21,
+ "autheticatorFlow": true,
+ "flowAlias": "Browser with user attribute Browser - Conditional OTP",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "id": "f1c208f3-c3fa-4237-be78-774e698b379d",
+ "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": "f1e9aeac-46b2-4ed2-b362-5f4a651d522b",
+ "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": "891b8796-4a38-4189-b462-d4976b7347b4",
+ "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": "684a0d69-07f6-4cc2-9e40-38b283fa1093",
+ "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": "fd028343-56cb-455c-9325-a363909edf5d",
+ "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": "100d0271-3c54-4ba1-9a37-b0078a82470c",
+ "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": "05e49cca-3eab-48db-b282-414ed61e37f9",
+ "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": "999d9553-e82b-4c06-918c-e82b4544e829",
+ "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": "7c4c1b16-5c7f-49d8-9603-4c277cdab76a",
+ "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": "b8db3d11-2123-4a26-add1-bc9ea269998d",
+ "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": "5d1329c6-23d2-4e14-83fb-bf5c98046a7a",
+ "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": "f28eceb4-1666-439c-b3f7-a4b27d66156d",
+ "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": "a1983ca1-0bd5-455f-9213-d4adfdc7be36",
+ "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": "ed976ba1-2643-4a0d-9459-02a4ea707535",
+ "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-profile-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 40,
+ "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
+ }
+ ]
+ },
+ {
+ "id": "1c016b4a-acdf-41a8-a02b-124e5bb83768",
+ "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": "eb3a81e5-2e3d-490c-8898-9cca7d387cd4",
+ "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": "6d7f7e83-1ec2-4ddb-a8c3-55c5f27a96d5",
+ "alias": "create unique user config",
+ "config": {
+ "require.password.update.after.registration": "false"
+ }
+ },
+ {
+ "id": "fb542612-8bc7-43dd-8cc0-b74493100e67",
+ "alias": "review profile config",
+ "config": {
+ "update.profile.on.first.login": "missing"
+ }
+ },
+ {
+ "id": "7eb11994-09b3-4df2-807b-e1b1b5ce9cea",
+ "alias": "username password favorite number form config",
+ "config": {
+ "user_attribute": "favorite_number",
+ "generate_label": "true",
+ "user_attribute_label": ""
+ }
+ }
+ ],
+ "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": "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",
+ "attributes": {
+ "cibaBackchannelTokenDeliveryMode": "poll",
+ "cibaExpiresIn": "120",
+ "cibaAuthRequestedUserHint": "login_hint",
+ "oauth2DeviceCodeLifespan": "600",
+ "oauth2DevicePollingInterval": "5",
+ "parRequestUriLifespan": "60",
+ "cibaInterval": "5",
+ "realmReusableOtpCode": "false"
+ },
+ "keycloakVersion": "22.0.3",
+ "userManagedAccessAllowed": false,
+ "clientProfiles": {
+ "profiles": []
+ },
+ "clientPolicies": {
+ "policies": []
+ },
+ "users" : [ {
+ "id" : "462c7cb4-e444-4eac-8d68-0686c30eb245",
+ "createdTimestamp" : 1621784842260,
+ "username" : "test",
+ "enabled" : true,
+ "totp" : false,
+ "emailVerified" : false,
+ "attributes" : {
+ "favorite_number" : [ "46" ]
+ },
+ "credentials" : [ {
+ "id" : "a0f53e85-a522-4ab4-85cb-95224dec8d35",
+ "type" : "password",
+ "createdDate" : 1621784863307,
+ "secretData" : "{\"value\":\"MFcHPyUSTiRqMhJum6z9KSIZbwJZAUswqq1zoVjoA6Cse8iylnjw9fOkEO72IWgS+PIj3RW7WB/CUp2deW8Swg==\",\"salt\":\"WztZBrFIbqNmEMGkQer7eQ==\",\"additionalParameters\":{}}",
+ "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
+ } ],
+ "requiredActions" : [ ],
+ "realmRoles" : [ "default-roles-dev-realm" ],
+ "notBefore" : 0,
+ "groups" : [ ]
+ } ]
+}
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index e63ea03..5becee6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -2,19 +2,23 @@ version: '3'
services:
keycloak:
- image: kilmajster/keycloak-with-authenticator:test
- container_name: keycloak-dev
- build:
- context: .
- dockerfile: src/main/docker/dev.Dockerfile
- ports:
- - 8081:8080
- - 8787:8787
- volumes:
- - ./src/test/resources/dev-realm.json:/tmp/dev-realm.json
+ container_name: keycloak
+ image: quay.io/keycloak/keycloak:24.0.1
+ entrypoint: [ "/opt/keycloak/bin/kc.sh", "--verbose", "start-dev", "--import-realm" ]
environment:
DEBUG: 'true'
DEBUG_PORT: '*:8787'
- KEYCLOAK_USER: admin
- KEYCLOAK_PASSWORD: admin
- KEYCLOAK_IMPORT: /tmp/dev-realm.json
+ KC_PROXY: edge
+ KC_HTTP_PORT: 8080
+ KEYCLOAK_ADMIN: admin
+ KEYCLOAK_ADMIN_PASSWORD: admin
+ volumes:
+ - type: bind
+ source: ./target
+ target: /opt/keycloak/providers
+ - type: bind
+ source: ./dev-realm.json
+ target: /opt/keycloak/data/import/dev-realm.json
+ ports:
+ - '8080:8080'
+ - '8787:8787'
diff --git a/pom.xml b/pom.xml
index 92c8a72..b747ff7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,15 +12,10 @@
jar
-
Default Keycloak login form with additional user attribute validation️
+
Default Keycloak login form with additional user attribute validation
https://github.com/kilmajster/keycloak-username-password-attribute-authenticator
2021
-
- github
- https://github.com/kilmajster/keycloak-username-password-attribute-authenticator/issues
-
-
MIT
@@ -29,71 +24,21 @@
-
-
- Łukasz Włódarczyk
- lukasz.createam@gmail.com
- 🇵🇱
-
-
-
-
- Github
- https://github.com/kilmajster/keycloak-username-password-attribute-authenticator/actions
-
-
-
- scm:git:git://github.com/kilmajster/keycloak-username-password-attribute-authenticator.git
- scm:git:ssh://github.com:kilmajster/keycloak-username-password-attribute-authenticator.git
- http://github.com/kilmajster/keycloak-username-password-attribute-authenticator
-
-
-
-
- ossrh
- Central Repository OSSRH
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
-
-
-
UTF-8
+ 17
+ 17
- 11
- 11
-
- 16.1.1
-
-
- 3.19.0
- 6.10.4
- 4.13.2
- 3.141.59
- 5.21.0
- 1.15.3
- 1.7.0
+ 24.0.1
-
- 3.8.1
- 3.0.1
- 3.2.0
- 3.3.0
- 3.2.1
- 2.22.2
+ 3.12.1
+ 3.1.0
+ 3.3.0
+ 3.6.3
+ 3.3.0
+ 3.2.5
-
-
-
- org.keycloak
- keycloak-parent
- ${keycloak.version}
- pom
- import
-
-
-
-
org.keycloak
@@ -113,162 +58,6 @@
${keycloak.version}
provided
-
-
-
-
- junit
- junit
- ${junit.version}
- test
-
-
- org.assertj
- assertj-core
- ${assertj.version}
- test
-
-
- org.testcontainers
- testcontainers
- ${testcontainers.version}
- test
-
-
- org.testcontainers
- selenium
- ${testcontainers.version}
- test
-
-
- com.github.dasniko
- testcontainers-keycloak
- ${testcontainers-keycloak.version}
- test
-
-
- com.codeborne
- selenide
- ${selenide.version}
- test
-
-
- org.seleniumhq.selenium
- selenium-chrome-driver
- ${selenium-chrome-driver.version}
- test
-
-
- io.cucumber
- cucumber-java
- ${cucumber.version}
- test
-
-
- io.cucumber
- cucumber-junit
- ${cucumber.version}
- test
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- ${maven-jar-plugin.version}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- automation-tests
-
-
-
- maven-surefire-plugin
- ${maven-surefire-plugin.version}
-
- **/CucumberConfig.java
-
-
-
-
-
-
- ossrh
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- ${maven-compiler-plugin.version}
-
- ${maven.compiler.source}
- ${maven.compiler.target}
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- ${maven-javadoc-plugin.version}
-
- src/main/java
-
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
- ${maven-source-plugin.version}
-
-
- attach-sources
-
- jar-no-fork
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- ${maven-gpg-plugin.version}
-
-
- --pinentry-mode
- loopback
-
-
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/docker/dev.Dockerfile b/src/main/docker/dev.Dockerfile
deleted file mode 100644
index 7ddebec..0000000
--- a/src/main/docker/dev.Dockerfile
+++ /dev/null
@@ -1,5 +0,0 @@
-FROM jboss/keycloak:16.1.1
-
-ARG VERSION=SNAPSHOT
-
-ADD target/keycloak-username-password-attribute-authenticator-${VERSION}.jar /opt/jboss/keycloak/standalone/deployments
\ No newline at end of file
diff --git a/src/main/docker/initContainer.Dockerfile b/src/main/docker/initContainer.Dockerfile
deleted file mode 100644
index b16771d..0000000
--- a/src/main/docker/initContainer.Dockerfile
+++ /dev/null
@@ -1,5 +0,0 @@
-FROM busybox
-
-ARG VERSION
-
-COPY target/keycloak-username-password-attribute-authenticator-${VERSION}.jar /opt/jboss/keycloak/standalone/deployments/
\ No newline at end of file
diff --git a/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeForm.java b/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeForm.java
index 08f04ef..25da0b3 100644
--- a/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeForm.java
+++ b/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeForm.java
@@ -1,28 +1,20 @@
package io.github.kilmajster.keycloak;
-import io.github.kilmajster.keycloak.ui.UserAttributeLabelGenerator;
-import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
-import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.forms.login.LoginFormsProvider;
-import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
-import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.ServicesLogger;
-import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.messages.Messages;
-import org.keycloak.services.validation.Validation;
-
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
import static io.github.kilmajster.keycloak.UsernamePasswordAttributeFormConfiguration.*;
+import static org.keycloak.services.validation.Validation.FIELD_PASSWORD;
public class UsernamePasswordAttributeForm extends UsernamePasswordForm implements Authenticator {
@@ -30,61 +22,40 @@ public class UsernamePasswordAttributeForm extends UsernamePasswordForm implemen
@Override
protected Response challenge(AuthenticationFlowContext context, String error, String field) {
- LoginFormsProvider form = context.form().setExecution(context.getExecution().getId());
- if (error != null) {
- if (field != null) {
- form.addError(new FormMessage(field, error));
- } else {
- form.setError(error, new Object[0]);
- }
- }
-
- configureUserAttributeLabel(context);
+ setUserAttributeFormLabel(context);
+ setUserAttributeFormErrorMessage(context);
- return createLoginForm(form);
+ return super.challenge(context, null, null);
}
@Override
protected Response challenge(AuthenticationFlowContext context, MultivaluedMap
formData) {
- LoginFormsProvider forms = context.form();
- if (formData.size() > 0) {
- forms.setFormData(formData);
- }
+ setUserAttributeFormLabel(context);
- configureUserAttributeLabel(context);
-
- return forms.createLoginUsernamePassword();
+ return super.challenge(context, formData);
}
@Override
- public void authenticate(AuthenticationFlowContext context) {
- MultivaluedMap formData = new MultivaluedMapImpl();
- String loginHint = context.getAuthenticationSession().getClientNote("login_hint");
- String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
- if (loginHint != null || rememberMeUsername != null) {
- if (loginHint != null) {
- formData.add("username", loginHint);
- } else {
- formData.add("username", rememberMeUsername);
- formData.add("rememberMe", "on");
- }
- }
-
- Response challengeResponse = challenge(context, formData);
- context.challenge(challengeResponse);
+ protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap formData) {
+ return super.validateForm(context, formData)
+ && isUserAttributeValid(context, formData.getFirst(USER_ATTRIBUTE));
}
- private void configureUserAttributeLabel(AuthenticationFlowContext context) {
- final String userAttributeLabel = configPropertyOf(context, LOGIN_FORM_ATTRIBUTE_LABEL);
+ private void setUserAttributeFormLabel(AuthenticationFlowContext context) {
+ String userAttributeLabel = configPropertyOf(context, USER_ATTRIBUTE_LABEL);
+
+ // label
if (userAttributeLabel != null) {
- context.form().setAttribute(LOGIN_FORM_ATTRIBUTE_LABEL, userAttributeLabel);
+ context.form().setAttribute(USER_ATTRIBUTE_LABEL, userAttributeLabel);
} else {
- final String userAttributeName = configPropertyOf(context, LOGIN_FORM_USER_ATTRIBUTE);
+ String userAttributeName = configPropertyOf(context, USER_ATTRIBUTE);
if (userAttributeName != null && !userAttributeName.isEmpty()) {
- context.form().setAttribute(LOGIN_FORM_ATTRIBUTE_LABEL,
+ context.form().setAttribute(
+ USER_ATTRIBUTE_LABEL,
isGenerateLabelEnabled(context)
- ? UserAttributeLabelGenerator.generateLabel(userAttributeName)
- : userAttributeName);
+ ? generateLabel(userAttributeName, true)
+ : userAttributeName
+ );
} else {
log.warn("Configuration of keycloak-user-attribute-authenticator is incomplete! " +
"At least user_attribute property needs to be set!");
@@ -92,124 +63,70 @@ private void configureUserAttributeLabel(AuthenticationFlowContext context) {
}
}
- @Override
- protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap formData) {
- context.clearUser();
- UserModel user = getUser(context, formData);
- return user != null
- && validatePassword(context, user, formData) && validateUser(context, user, formData)
- && validateUserAttribute(context, user, formData);
- }
+ private void setUserAttributeFormErrorMessage(AuthenticationFlowContext context) {
+ String userAttributeName = configPropertyOf(context, USER_ATTRIBUTE);
+ String userAttributeErrorMessage = configPropertyOf(context, USER_ATTRIBUTE_ERROR_MESSAGE);
- private boolean validateUserAttribute(AuthenticationFlowContext context, UserModel user, MultivaluedMap formData) {
- final String providedAttribute = formData.getFirst(LOGIN_FORM_USER_ATTRIBUTE);
- if (providedAttribute == null || providedAttribute.isEmpty()) {
- return invalidUserAttributeHandler(context, user, true);
- }
-
- if (isProvidedAttributeValid(context, user, providedAttribute)) {
- return true;
+ if (userAttributeErrorMessage != null) {
+ context.form().addError(
+ new FormMessage(FIELD_PASSWORD, userAttributeErrorMessage, userAttributeName)
+ );
} else {
- return invalidUserAttributeHandler(context, user, false);
+ if (userAttributeName != null && !userAttributeName.isEmpty()) {
+ context.form().addError(
+ new FormMessage(
+ FIELD_PASSWORD,
+ "invalidUsernamePasswordOrAttributeMessage",
+ isGenerateLabelEnabled(context)
+ ? generateLabel(userAttributeName, false)
+ : userAttributeName
+ )
+ );
+ } else {
+ log.warn("Configuration of keycloak-user-attribute-authenticator is incomplete! " +
+ "At least user_attribute property needs to be set!");
+ }
}
}
- private boolean isProvidedAttributeValid(AuthenticationFlowContext context, UserModel user, String providedUserAttribute) {
- String userAttributeName = context.getAuthenticatorConfig().getConfig().get(LOGIN_FORM_USER_ATTRIBUTE);
- return user.getAttributeStream(userAttributeName)
- .anyMatch(attr -> attr.equals(providedUserAttribute));
+ private boolean isUserAttributeValid(AuthenticationFlowContext context, String providedAttribute) {
+ String attributeName = context.getAuthenticatorConfig().getConfig().get(USER_ATTRIBUTE);
+ UserModel user = context.getUser();
+ boolean attributeValid = user != null
+ && user.getAttributeStream(attributeName).anyMatch(attr -> attr.equals(providedAttribute));
+
+ return attributeValid || badAttributeHandler(context, user);
}
- // Set up AuthenticationFlowContext error.
- private boolean invalidUserAttributeHandler(AuthenticationFlowContext context, UserModel user, boolean isAttributeEmpty) {
+ private boolean badAttributeHandler(AuthenticationFlowContext context, UserModel user) {
context.getEvent().user(user);
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
+ context.getEvent().detail(Details.REASON, "Invalid user attribute was provided");
- String errorText;
- final String configuredErrorMessage = configPropertyOf(context, LOGIN_FORM_ERROR_MESSAGE);
- if (configuredErrorMessage != null && !configuredErrorMessage.isBlank()) {
- // error text directly from error label propertyModelException
- errorText = configuredErrorMessage;
- } else {
- final String userAttributeLabel = configPropertyOf(context, LOGIN_FORM_ATTRIBUTE_LABEL);
- if (userAttributeLabel != null && !userAttributeLabel.isBlank()) {
- // get message from message.properties in case USER_ATTRIBUTE_LABEL is a message key
- final String message = context.form().getMessage(userAttributeLabel);
- // generating error message based on provided user attribute label
- errorText = UserAttributeLabelGenerator.generateErrorText(message != null ? message : userAttributeLabel);
- } else {
- // user attribute label not provided so generating text based on attribute name
- errorText = isGenerateLabelEnabled(context) // generate pretty error if property is not disabled
- ? UserAttributeLabelGenerator.generateErrorText(configPropertyOf(context, LOGIN_FORM_USER_ATTRIBUTE))
- : "Invalid ".concat(configPropertyOf(context, LOGIN_FORM_USER_ATTRIBUTE)); // use raw attribute name
- }
- }
-
- if (isClearUserOnFailedAttributeValidationEnabled(context)) {
- context.clearUser();
- }
-
- Response challengeResponse = challenge(context, errorText, LOGIN_FORM_USER_ATTRIBUTE);
-
- if (isAttributeEmpty) {
- context.forceChallenge(challengeResponse);
- } else {
- context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
+ if (isUserAlreadySetBeforeUsernamePasswordAuth(context)) {
+ LoginFormsProvider form = context.form();
+ form.setAttribute(LoginFormsProvider.USERNAME_HIDDEN, true);
+ form.setAttribute(LoginFormsProvider.REGISTRATION_DISABLED, true);
}
+ Response challengeResponse = challenge(context, getDefaultChallengeMessage(context), FIELD_PASSWORD);
+ context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
context.clearUser();
return false;
}
+ private String generateLabel(final String attributeName, boolean capitalize) {
+ final String lowercaseWithSpaces = attributeName
+ .toLowerCase()
+ .replace(".", " ")
+ .replace("_", " ")
+ .replace("-", " ");
- private UserModel getUser(AuthenticationFlowContext context, MultivaluedMap formData) {
- String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
- if (username == null) {
- context.getEvent().error(Errors.USER_NOT_FOUND);
- Response challengeResponse = challenge(context, getDefaultChallengeMessage(context), Validation.FIELD_USERNAME);
- context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse);
- return null;
- }
-
- // remove leading and trailing whitespace
- username = username.trim();
-
- context.getEvent().detail(Details.USERNAME, username);
- context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username);
-
- UserModel user = null;
- try {
- user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
- } catch (ModelDuplicateException mde) {
- ServicesLogger.LOGGER.modelDuplicateException(mde);
-
- // Could happen during federation import
- if (mde.getDuplicateFieldName() != null && mde.getDuplicateFieldName().equals(UserModel.EMAIL)) {
- setDuplicateUserChallenge(context, Errors.EMAIL_IN_USE, Messages.EMAIL_EXISTS, AuthenticationFlowError.INVALID_USER);
- } else {
- setDuplicateUserChallenge(context, Errors.USERNAME_IN_USE, Messages.USERNAME_EXISTS, AuthenticationFlowError.INVALID_USER);
- }
- return user;
- }
-
- testInvalidUser(context, user);
- return user;
+ return capitalize ? capitalize(lowercaseWithSpaces) : lowercaseWithSpaces;
}
- private boolean validateUser(AuthenticationFlowContext context, UserModel user, MultivaluedMap inputData) {
- if (!enabledUser(context, user)) {
- return false;
- }
- String rememberMe = inputData.getFirst("rememberMe");
- boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
- if (remember) {
- context.getAuthenticationSession().setAuthNote(Details.REMEMBER_ME, "true");
- context.getEvent().detail(Details.REMEMBER_ME, "true");
- } else {
- context.getAuthenticationSession().removeAuthNote(Details.REMEMBER_ME);
- }
- context.setUser(user);
- return true;
+ private String capitalize(final String toCapitalize) {
+ return toCapitalize.substring(0, 1).toUpperCase() + toCapitalize.substring(1).toLowerCase();
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormConfiguration.java b/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormConfiguration.java
index b322c55..5b5761e 100644
--- a/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormConfiguration.java
+++ b/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormConfiguration.java
@@ -9,52 +9,43 @@
public interface UsernamePasswordAttributeFormConfiguration {
- String LOGIN_FORM_USER_ATTRIBUTE = "login_form_user_attribute";
- String LOGIN_FORM_GENERATE_LABEL = "login_form_generate_label";
- String LOGIN_FORM_ATTRIBUTE_LABEL = "login_form_attribute_label";
- String LOGIN_FORM_ERROR_MESSAGE = "login_form_error_message";
- String CLEAR_USER_ON_ATTRIBUTE_VALIDATION_FAIL = "clear_user_on_attribute_validation_fail";
+ String USER_ATTRIBUTE = "user_attribute";
+ String GENERATE_LABEL = "generate_label";
+ String USER_ATTRIBUTE_LABEL = "user_attribute_label";
+ String USER_ATTRIBUTE_ERROR_MESSAGE = "user_attribute_error_message";
List PROPS = ProviderConfigurationBuilder.create()
.property()
- .name(LOGIN_FORM_USER_ATTRIBUTE)
+ .name(USER_ATTRIBUTE)
.type(ProviderConfigProperty.STRING_TYPE)
.label("User attribute")
.helpText("Attribute used to validate login form.")
.add()
.property()
- .name(LOGIN_FORM_GENERATE_LABEL)
+ .name(GENERATE_LABEL)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.label("Generate label")
.defaultValue("true") // only string value is accepted
.helpText("If enabled, label for login form will be generated based on attribute name, so attribute with name:" +
- " \"foot_size\" will be labeled as \"Foot size\", \"REALLY_custom.user-Attribute\" will be translated " +
+ " \"favorite_number\" will be labeled as \"Favorite number\", \"REALLY_custom.user-Attribute\" will be translated " +
"to \"Really custom user attribute\", etc. By default, set to true. If User attribute form label " +
"is configured, label is taken form configuration and generation is skipped.")
.add()
.property()
- .name(CLEAR_USER_ON_ATTRIBUTE_VALIDATION_FAIL)
- .type(ProviderConfigProperty.BOOLEAN_TYPE)
- .label("Clear user on validation fail")
- .defaultValue("true") // only string value is accepted
- .helpText("If enabled, user is not stored in session context in case username and password were valid but user attribute was not.")
- .add()
-
- .property()
- .name(LOGIN_FORM_ATTRIBUTE_LABEL)
+ .name(USER_ATTRIBUTE_LABEL)
.type(ProviderConfigProperty.STRING_TYPE)
.label("User attribute form label")
.helpText("Message which will be displayed as user attribute input label. If value is a valid message key, then proper translation will be used.")
.add()
.property()
- .name(LOGIN_FORM_ERROR_MESSAGE)
+ .name(USER_ATTRIBUTE_ERROR_MESSAGE)
.type(ProviderConfigProperty.STRING_TYPE)
- .label("Validation error message")
- .helpText("Message which will be displayed as user attribute validation error. If value is a valid message key, then proper translation will be used.")
+ .label("Invalid user attribute error message")
+ .helpText("Message which will be displayed for invalid user attribute error message. If value is a valid message key, then proper translation will be used.")
.add()
.build();
@@ -65,10 +56,6 @@ static String configPropertyOf(final AuthenticationFlowContext context, final St
}
static boolean isGenerateLabelEnabled(final AuthenticationFlowContext context) {
- return Boolean.parseBoolean(configPropertyOf(context, LOGIN_FORM_GENERATE_LABEL));
- }
-
- static boolean isClearUserOnFailedAttributeValidationEnabled(final AuthenticationFlowContext context) {
- return Boolean.parseBoolean(configPropertyOf(context, CLEAR_USER_ON_ATTRIBUTE_VALIDATION_FAIL));
+ return Boolean.parseBoolean(configPropertyOf(context, GENERATE_LABEL));
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormFactory.java b/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormFactory.java
index d7df8d0..c2c4bda 100644
--- a/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormFactory.java
+++ b/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormFactory.java
@@ -1,11 +1,8 @@
package io.github.kilmajster.keycloak;
import org.keycloak.Config;
-import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
-import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
-import org.keycloak.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticator;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -14,7 +11,7 @@
import java.util.List;
-public class UsernamePasswordAttributeFormFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
+public class UsernamePasswordAttributeFormFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "auth-username-password-attr-form";
public static final UsernamePasswordAttributeForm SINGLETON = new UsernamePasswordAttributeForm();
@@ -29,13 +26,6 @@ public List getConfigProperties() {
return UsernamePasswordAttributeFormConfiguration.PROPS;
}
- @Override
- public Authenticator createDisplay(KeycloakSession session, String displayType) {
- if (displayType == null) return SINGLETON;
- if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
- return ConsoleUsernamePasswordAuthenticator.SINGLETON;
- }
-
@Override
public void init(Config.Scope config) {
@@ -67,7 +57,8 @@ public boolean isConfigurable() {
}
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
- AuthenticationExecutionModel.Requirement.REQUIRED
+ AuthenticationExecutionModel.Requirement.REQUIRED,
+ AuthenticationExecutionModel.Requirement.DISABLED,
};
@Override
diff --git a/src/main/java/io/github/kilmajster/keycloak/ui/UserAttributeLabelGenerator.java b/src/main/java/io/github/kilmajster/keycloak/ui/UserAttributeLabelGenerator.java
deleted file mode 100644
index 4f60c73..0000000
--- a/src/main/java/io/github/kilmajster/keycloak/ui/UserAttributeLabelGenerator.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package io.github.kilmajster.keycloak.ui;
-
-public final class UserAttributeLabelGenerator {
-
- public static String generateLabel(final String attributeName) {
- final String lowercaseWithSpaces = attributeName
- .toLowerCase()
- .replace(".", " ")
- .replace("_", " ")
- .replace("-", " ");
-
- return capitalizeFirstChar(lowercaseWithSpaces);
- }
-
- public static String generateErrorText(final String attributeName) {
- return "Invalid " + generateLabel(attributeName).toLowerCase() + ".";
- }
-
- private static String capitalizeFirstChar(final String lowercaseWithSpaces) {
- return lowercaseWithSpaces.substring(0,1).toUpperCase() + lowercaseWithSpaces.substring(1).toLowerCase();
- }
-}
diff --git a/src/main/resources/theme/base-with-attribute/login/login.ftl b/src/main/resources/theme/base-with-attribute/login/login.ftl
index 4a7b1ae..e20147d 100644
--- a/src/main/resources/theme/base-with-attribute/login/login.ftl
+++ b/src/main/resources/theme/base-with-attribute/login/login.ftl
@@ -3,114 +3,135 @@
<#if section = "header">
${msg("loginAccountTitle")}
<#elseif section = "form">
-