119 | ```
120 |
121 | ### Testing & development
122 | #### Build the project
123 | ```shell
124 | $ mvn package
125 | ```
126 | #### Run Keycloak with authenticator in docker compose
127 | After building a project, do following to start Keycloak with bundled authenticator jar and dummy configuration ([`dev-realm.json`](dev-realm.json)).
128 | ```shell
129 | $ docker compose up
130 | ```
131 | Open browser and go to http://localhost:8080/realms/dev-realm/account
132 | use _Username or email_ = `test`, _Password_ = `test` and _Favorite number_ = `46` to login.
133 |
--------------------------------------------------------------------------------
/dev-realm.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "834073ca-9a3c-4e36-aa47-f8866b247935",
3 | "realm": "dev-realm",
4 | "notBefore": 0,
5 | "defaultSignatureAlgorithm": "RS256",
6 | "revokeRefreshToken": false,
7 | "refreshTokenMaxReuse": 0,
8 | "accessTokenLifespan": 300,
9 | "accessTokenLifespanForImplicitFlow": 900,
10 | "ssoSessionIdleTimeout": 1800,
11 | "ssoSessionMaxLifespan": 36000,
12 | "ssoSessionIdleTimeoutRememberMe": 0,
13 | "ssoSessionMaxLifespanRememberMe": 0,
14 | "offlineSessionIdleTimeout": 2592000,
15 | "offlineSessionMaxLifespanEnabled": false,
16 | "offlineSessionMaxLifespan": 5184000,
17 | "clientSessionIdleTimeout": 0,
18 | "clientSessionMaxLifespan": 0,
19 | "clientOfflineSessionIdleTimeout": 0,
20 | "clientOfflineSessionMaxLifespan": 0,
21 | "accessCodeLifespan": 60,
22 | "accessCodeLifespanUserAction": 300,
23 | "accessCodeLifespanLogin": 1800,
24 | "actionTokenGeneratedByAdminLifespan": 43200,
25 | "actionTokenGeneratedByUserLifespan": 300,
26 | "oauth2DeviceCodeLifespan": 600,
27 | "oauth2DevicePollingInterval": 5,
28 | "enabled": true,
29 | "sslRequired": "external",
30 | "registrationAllowed": false,
31 | "registrationEmailAsUsername": false,
32 | "rememberMe": false,
33 | "verifyEmail": false,
34 | "loginWithEmailAllowed": true,
35 | "duplicateEmailsAllowed": false,
36 | "resetPasswordAllowed": false,
37 | "editUsernameAllowed": false,
38 | "bruteForceProtected": false,
39 | "permanentLockout": false,
40 | "maxFailureWaitSeconds": 900,
41 | "minimumQuickLoginWaitSeconds": 60,
42 | "waitIncrementSeconds": 60,
43 | "quickLoginCheckMilliSeconds": 1000,
44 | "maxDeltaTimeSeconds": 43200,
45 | "failureFactor": 30,
46 | "defaultRole": {
47 | "id": "2fe40b1b-b89a-402a-8ce6-566a78268bc4",
48 | "name": "default-roles-dev-realm",
49 | "description": "${role_default-roles}",
50 | "composite": true,
51 | "clientRole": false,
52 | "containerId": "834073ca-9a3c-4e36-aa47-f8866b247935"
53 | },
54 | "requiredCredentials": [
55 | "password"
56 | ],
57 | "otpPolicyType": "totp",
58 | "otpPolicyAlgorithm": "HmacSHA1",
59 | "otpPolicyInitialCounter": 0,
60 | "otpPolicyDigits": 6,
61 | "otpPolicyLookAheadWindow": 1,
62 | "otpPolicyPeriod": 30,
63 | "otpPolicyCodeReusable": false,
64 | "otpSupportedApplications": [
65 | "totpAppMicrosoftAuthenticatorName",
66 | "totpAppFreeOTPName",
67 | "totpAppGoogleName"
68 | ],
69 | "webAuthnPolicyRpEntityName": "keycloak",
70 | "webAuthnPolicySignatureAlgorithms": [
71 | "ES256"
72 | ],
73 | "webAuthnPolicyRpId": "",
74 | "webAuthnPolicyAttestationConveyancePreference": "not specified",
75 | "webAuthnPolicyAuthenticatorAttachment": "not specified",
76 | "webAuthnPolicyRequireResidentKey": "not specified",
77 | "webAuthnPolicyUserVerificationRequirement": "not specified",
78 | "webAuthnPolicyCreateTimeout": 0,
79 | "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
80 | "webAuthnPolicyAcceptableAaguids": [],
81 | "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
82 | "webAuthnPolicyPasswordlessSignatureAlgorithms": [
83 | "ES256"
84 | ],
85 | "webAuthnPolicyPasswordlessRpId": "",
86 | "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
87 | "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
88 | "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
89 | "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
90 | "webAuthnPolicyPasswordlessCreateTimeout": 0,
91 | "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
92 | "webAuthnPolicyPasswordlessAcceptableAaguids": [],
93 | "scopeMappings": [
94 | {
95 | "clientScope": "offline_access",
96 | "roles": [
97 | "offline_access"
98 | ]
99 | }
100 | ],
101 | "clientScopeMappings": {
102 | "account": [
103 | {
104 | "client": "account-console",
105 | "roles": [
106 | "manage-account",
107 | "view-groups"
108 | ]
109 | }
110 | ]
111 | },
112 | "clients": [
113 | {
114 | "id": "6c84289f-d0ea-4651-b6a9-dc9cb06ce9a8",
115 | "clientId": "account",
116 | "name": "${client_account}",
117 | "rootUrl": "${authBaseUrl}",
118 | "baseUrl": "/realms/dev-realm/account/",
119 | "surrogateAuthRequired": false,
120 | "enabled": true,
121 | "alwaysDisplayInConsole": false,
122 | "clientAuthenticatorType": "client-secret",
123 | "redirectUris": [
124 | "/realms/dev-realm/account/*"
125 | ],
126 | "webOrigins": [],
127 | "notBefore": 0,
128 | "bearerOnly": false,
129 | "consentRequired": false,
130 | "standardFlowEnabled": true,
131 | "implicitFlowEnabled": false,
132 | "directAccessGrantsEnabled": false,
133 | "serviceAccountsEnabled": false,
134 | "publicClient": true,
135 | "frontchannelLogout": false,
136 | "protocol": "openid-connect",
137 | "attributes": {
138 | "post.logout.redirect.uris": "+"
139 | },
140 | "authenticationFlowBindingOverrides": {},
141 | "fullScopeAllowed": false,
142 | "nodeReRegistrationTimeout": 0,
143 | "defaultClientScopes": [
144 | "web-origins",
145 | "acr",
146 | "roles",
147 | "profile",
148 | "email"
149 | ],
150 | "optionalClientScopes": [
151 | "address",
152 | "phone",
153 | "offline_access",
154 | "microprofile-jwt"
155 | ]
156 | },
157 | {
158 | "id": "1a60274d-756d-4351-8c5e-a41769834c49",
159 | "clientId": "account-console",
160 | "name": "${client_account-console}",
161 | "description": "",
162 | "rootUrl": "${authBaseUrl}",
163 | "adminUrl": "",
164 | "baseUrl": "/realms/dev-realm/account/",
165 | "surrogateAuthRequired": false,
166 | "enabled": true,
167 | "alwaysDisplayInConsole": false,
168 | "clientAuthenticatorType": "client-secret",
169 | "redirectUris": [
170 | "/realms/dev-realm/account/*"
171 | ],
172 | "webOrigins": [],
173 | "notBefore": 0,
174 | "bearerOnly": false,
175 | "consentRequired": false,
176 | "standardFlowEnabled": true,
177 | "implicitFlowEnabled": false,
178 | "directAccessGrantsEnabled": false,
179 | "serviceAccountsEnabled": false,
180 | "publicClient": true,
181 | "frontchannelLogout": false,
182 | "protocol": "openid-connect",
183 | "attributes": {
184 | "login_theme": "base-with-attribute",
185 | "post.logout.redirect.uris": "+",
186 | "oauth2.device.authorization.grant.enabled": "false",
187 | "backchannel.logout.revoke.offline.tokens": "false",
188 | "use.refresh.tokens": "true",
189 | "oidc.ciba.grant.enabled": "false",
190 | "backchannel.logout.session.required": "true",
191 | "client_credentials.use_refresh_token": "false",
192 | "tls.client.certificate.bound.access.tokens": "false",
193 | "require.pushed.authorization.requests": "false",
194 | "acr.loa.map": "{}",
195 | "display.on.consent.screen": "false",
196 | "pkce.code.challenge.method": "S256",
197 | "token.response.type.bearer.lower-case": "false"
198 | },
199 | "authenticationFlowBindingOverrides": {
200 | "browser": "f67cafc8-d118-4824-9cc5-a704f6e53d32"
201 | },
202 | "fullScopeAllowed": false,
203 | "nodeReRegistrationTimeout": 0,
204 | "protocolMappers": [
205 | {
206 | "id": "c248feec-49a5-49ab-b2c1-c513cc55731c",
207 | "name": "audience resolve",
208 | "protocol": "openid-connect",
209 | "protocolMapper": "oidc-audience-resolve-mapper",
210 | "consentRequired": false,
211 | "config": {}
212 | }
213 | ],
214 | "defaultClientScopes": [
215 | "web-origins",
216 | "acr",
217 | "roles",
218 | "profile",
219 | "email"
220 | ],
221 | "optionalClientScopes": [
222 | "address",
223 | "phone",
224 | "offline_access",
225 | "microprofile-jwt"
226 | ]
227 | },
228 | {
229 | "id": "ca55cdf2-5e24-473d-9ed3-7d3415638d65",
230 | "clientId": "admin-cli",
231 | "name": "${client_admin-cli}",
232 | "surrogateAuthRequired": false,
233 | "enabled": true,
234 | "alwaysDisplayInConsole": false,
235 | "clientAuthenticatorType": "client-secret",
236 | "redirectUris": [],
237 | "webOrigins": [],
238 | "notBefore": 0,
239 | "bearerOnly": false,
240 | "consentRequired": false,
241 | "standardFlowEnabled": false,
242 | "implicitFlowEnabled": false,
243 | "directAccessGrantsEnabled": true,
244 | "serviceAccountsEnabled": false,
245 | "publicClient": true,
246 | "frontchannelLogout": false,
247 | "protocol": "openid-connect",
248 | "attributes": {},
249 | "authenticationFlowBindingOverrides": {},
250 | "fullScopeAllowed": false,
251 | "nodeReRegistrationTimeout": 0,
252 | "defaultClientScopes": [
253 | "web-origins",
254 | "acr",
255 | "roles",
256 | "profile",
257 | "email"
258 | ],
259 | "optionalClientScopes": [
260 | "address",
261 | "phone",
262 | "offline_access",
263 | "microprofile-jwt"
264 | ]
265 | },
266 | {
267 | "id": "a52ee408-0952-4913-b572-d8a343e1684b",
268 | "clientId": "broker",
269 | "name": "${client_broker}",
270 | "surrogateAuthRequired": false,
271 | "enabled": true,
272 | "alwaysDisplayInConsole": false,
273 | "clientAuthenticatorType": "client-secret",
274 | "redirectUris": [],
275 | "webOrigins": [],
276 | "notBefore": 0,
277 | "bearerOnly": true,
278 | "consentRequired": false,
279 | "standardFlowEnabled": true,
280 | "implicitFlowEnabled": false,
281 | "directAccessGrantsEnabled": false,
282 | "serviceAccountsEnabled": false,
283 | "publicClient": false,
284 | "frontchannelLogout": false,
285 | "protocol": "openid-connect",
286 | "attributes": {},
287 | "authenticationFlowBindingOverrides": {},
288 | "fullScopeAllowed": false,
289 | "nodeReRegistrationTimeout": 0,
290 | "defaultClientScopes": [
291 | "web-origins",
292 | "acr",
293 | "roles",
294 | "profile",
295 | "email"
296 | ],
297 | "optionalClientScopes": [
298 | "address",
299 | "phone",
300 | "offline_access",
301 | "microprofile-jwt"
302 | ]
303 | },
304 | {
305 | "id": "4db9b7d3-fb96-4b14-a8cf-b2f75525f5c1",
306 | "clientId": "realm-management",
307 | "name": "${client_realm-management}",
308 | "surrogateAuthRequired": false,
309 | "enabled": true,
310 | "alwaysDisplayInConsole": false,
311 | "clientAuthenticatorType": "client-secret",
312 | "redirectUris": [],
313 | "webOrigins": [],
314 | "notBefore": 0,
315 | "bearerOnly": true,
316 | "consentRequired": false,
317 | "standardFlowEnabled": true,
318 | "implicitFlowEnabled": false,
319 | "directAccessGrantsEnabled": false,
320 | "serviceAccountsEnabled": false,
321 | "publicClient": false,
322 | "frontchannelLogout": false,
323 | "protocol": "openid-connect",
324 | "attributes": {},
325 | "authenticationFlowBindingOverrides": {},
326 | "fullScopeAllowed": false,
327 | "nodeReRegistrationTimeout": 0,
328 | "defaultClientScopes": [
329 | "web-origins",
330 | "acr",
331 | "roles",
332 | "profile",
333 | "email"
334 | ],
335 | "optionalClientScopes": [
336 | "address",
337 | "phone",
338 | "offline_access",
339 | "microprofile-jwt"
340 | ]
341 | },
342 | {
343 | "id": "6037cb4f-e522-4916-b2ef-18bbe37398cf",
344 | "clientId": "security-admin-console",
345 | "name": "${client_security-admin-console}",
346 | "rootUrl": "${authAdminUrl}",
347 | "baseUrl": "/admin/dev-realm/console/",
348 | "surrogateAuthRequired": false,
349 | "enabled": true,
350 | "alwaysDisplayInConsole": false,
351 | "clientAuthenticatorType": "client-secret",
352 | "redirectUris": [
353 | "/admin/dev-realm/console/*"
354 | ],
355 | "webOrigins": [
356 | "+"
357 | ],
358 | "notBefore": 0,
359 | "bearerOnly": false,
360 | "consentRequired": false,
361 | "standardFlowEnabled": true,
362 | "implicitFlowEnabled": false,
363 | "directAccessGrantsEnabled": false,
364 | "serviceAccountsEnabled": false,
365 | "publicClient": true,
366 | "frontchannelLogout": false,
367 | "protocol": "openid-connect",
368 | "attributes": {
369 | "post.logout.redirect.uris": "+",
370 | "pkce.code.challenge.method": "S256"
371 | },
372 | "authenticationFlowBindingOverrides": {},
373 | "fullScopeAllowed": false,
374 | "nodeReRegistrationTimeout": 0,
375 | "protocolMappers": [
376 | {
377 | "id": "22ba440a-238f-4786-9f14-d0243b093365",
378 | "name": "locale",
379 | "protocol": "openid-connect",
380 | "protocolMapper": "oidc-usermodel-attribute-mapper",
381 | "consentRequired": false,
382 | "config": {
383 | "userinfo.token.claim": "true",
384 | "user.attribute": "locale",
385 | "id.token.claim": "true",
386 | "access.token.claim": "true",
387 | "claim.name": "locale",
388 | "jsonType.label": "String"
389 | }
390 | }
391 | ],
392 | "defaultClientScopes": [
393 | "web-origins",
394 | "acr",
395 | "roles",
396 | "profile",
397 | "email"
398 | ],
399 | "optionalClientScopes": [
400 | "address",
401 | "phone",
402 | "offline_access",
403 | "microprofile-jwt"
404 | ]
405 | }
406 | ],
407 | "clientScopes": [
408 | {
409 | "id": "e1340e1d-18db-4b28-8d95-01b0efe85648",
410 | "name": "email",
411 | "description": "OpenID Connect built-in scope: email",
412 | "protocol": "openid-connect",
413 | "attributes": {
414 | "include.in.token.scope": "true",
415 | "display.on.consent.screen": "true",
416 | "consent.screen.text": "${emailScopeConsentText}"
417 | },
418 | "protocolMappers": [
419 | {
420 | "id": "731ebd0c-60fd-4334-82f5-c6b655bea46a",
421 | "name": "email verified",
422 | "protocol": "openid-connect",
423 | "protocolMapper": "oidc-usermodel-property-mapper",
424 | "consentRequired": false,
425 | "config": {
426 | "userinfo.token.claim": "true",
427 | "user.attribute": "emailVerified",
428 | "id.token.claim": "true",
429 | "access.token.claim": "true",
430 | "claim.name": "email_verified",
431 | "jsonType.label": "boolean"
432 | }
433 | },
434 | {
435 | "id": "8fff12dd-e855-4bdb-96c5-fa22d1f3c4b1",
436 | "name": "email",
437 | "protocol": "openid-connect",
438 | "protocolMapper": "oidc-usermodel-attribute-mapper",
439 | "consentRequired": false,
440 | "config": {
441 | "userinfo.token.claim": "true",
442 | "user.attribute": "email",
443 | "id.token.claim": "true",
444 | "access.token.claim": "true",
445 | "claim.name": "email",
446 | "jsonType.label": "String"
447 | }
448 | }
449 | ]
450 | },
451 | {
452 | "id": "fa673ddd-a855-4590-8d4f-ab13f7e247af",
453 | "name": "acr",
454 | "description": "OpenID Connect scope for add acr (authentication context class reference) to the token",
455 | "protocol": "openid-connect",
456 | "attributes": {
457 | "include.in.token.scope": "false",
458 | "display.on.consent.screen": "false"
459 | },
460 | "protocolMappers": [
461 | {
462 | "id": "f7e82b99-268a-4c4c-a7eb-bb62e578081d",
463 | "name": "acr loa level",
464 | "protocol": "openid-connect",
465 | "protocolMapper": "oidc-acr-mapper",
466 | "consentRequired": false,
467 | "config": {
468 | "id.token.claim": "true",
469 | "access.token.claim": "true"
470 | }
471 | }
472 | ]
473 | },
474 | {
475 | "id": "dd2cc4ab-9ad3-40f5-b874-6e3c9e0caefa",
476 | "name": "address",
477 | "description": "OpenID Connect built-in scope: address",
478 | "protocol": "openid-connect",
479 | "attributes": {
480 | "include.in.token.scope": "true",
481 | "display.on.consent.screen": "true",
482 | "consent.screen.text": "${addressScopeConsentText}"
483 | },
484 | "protocolMappers": [
485 | {
486 | "id": "7df1b49b-7640-4e1e-85b6-370557dc7b16",
487 | "name": "address",
488 | "protocol": "openid-connect",
489 | "protocolMapper": "oidc-address-mapper",
490 | "consentRequired": false,
491 | "config": {
492 | "user.attribute.formatted": "formatted",
493 | "user.attribute.country": "country",
494 | "user.attribute.postal_code": "postal_code",
495 | "userinfo.token.claim": "true",
496 | "user.attribute.street": "street",
497 | "id.token.claim": "true",
498 | "user.attribute.region": "region",
499 | "access.token.claim": "true",
500 | "user.attribute.locality": "locality"
501 | }
502 | }
503 | ]
504 | },
505 | {
506 | "id": "5419107b-28fd-442a-a4da-42f3a6ffdc54",
507 | "name": "role_list",
508 | "description": "SAML role list",
509 | "protocol": "saml",
510 | "attributes": {
511 | "consent.screen.text": "${samlRoleListScopeConsentText}",
512 | "display.on.consent.screen": "true"
513 | },
514 | "protocolMappers": [
515 | {
516 | "id": "5fe1d7c5-25c4-49a0-8821-ba0408f17570",
517 | "name": "role list",
518 | "protocol": "saml",
519 | "protocolMapper": "saml-role-list-mapper",
520 | "consentRequired": false,
521 | "config": {
522 | "single": "false",
523 | "attribute.nameformat": "Basic",
524 | "attribute.name": "Role"
525 | }
526 | }
527 | ]
528 | },
529 | {
530 | "id": "27824e4d-de85-48d1-a942-d427a7c6584b",
531 | "name": "web-origins",
532 | "description": "OpenID Connect scope for add allowed web origins to the access token",
533 | "protocol": "openid-connect",
534 | "attributes": {
535 | "include.in.token.scope": "false",
536 | "display.on.consent.screen": "false",
537 | "consent.screen.text": ""
538 | },
539 | "protocolMappers": [
540 | {
541 | "id": "dccadfa1-3772-492f-b365-1992efc50273",
542 | "name": "allowed web origins",
543 | "protocol": "openid-connect",
544 | "protocolMapper": "oidc-allowed-origins-mapper",
545 | "consentRequired": false,
546 | "config": {}
547 | }
548 | ]
549 | },
550 | {
551 | "id": "c82c6fcb-a954-4f73-8fea-9c882f26f8a1",
552 | "name": "offline_access",
553 | "description": "OpenID Connect built-in scope: offline_access",
554 | "protocol": "openid-connect",
555 | "attributes": {
556 | "consent.screen.text": "${offlineAccessScopeConsentText}",
557 | "display.on.consent.screen": "true"
558 | }
559 | },
560 | {
561 | "id": "5d2bc3ff-cb61-4df7-bef3-542fc524e84e",
562 | "name": "phone",
563 | "description": "OpenID Connect built-in scope: phone",
564 | "protocol": "openid-connect",
565 | "attributes": {
566 | "include.in.token.scope": "true",
567 | "display.on.consent.screen": "true",
568 | "consent.screen.text": "${phoneScopeConsentText}"
569 | },
570 | "protocolMappers": [
571 | {
572 | "id": "066eab59-34cb-466d-84ef-ace79cf38d53",
573 | "name": "phone number",
574 | "protocol": "openid-connect",
575 | "protocolMapper": "oidc-usermodel-attribute-mapper",
576 | "consentRequired": false,
577 | "config": {
578 | "userinfo.token.claim": "true",
579 | "user.attribute": "phoneNumber",
580 | "id.token.claim": "true",
581 | "access.token.claim": "true",
582 | "claim.name": "phone_number",
583 | "jsonType.label": "String"
584 | }
585 | },
586 | {
587 | "id": "c56590c7-8045-4f64-83d3-13eee3067892",
588 | "name": "phone number verified",
589 | "protocol": "openid-connect",
590 | "protocolMapper": "oidc-usermodel-attribute-mapper",
591 | "consentRequired": false,
592 | "config": {
593 | "userinfo.token.claim": "true",
594 | "user.attribute": "phoneNumberVerified",
595 | "id.token.claim": "true",
596 | "access.token.claim": "true",
597 | "claim.name": "phone_number_verified",
598 | "jsonType.label": "boolean"
599 | }
600 | }
601 | ]
602 | },
603 | {
604 | "id": "a4d1d6ec-51ca-4bf1-b15b-53ad31b968d3",
605 | "name": "roles",
606 | "description": "OpenID Connect scope for add user roles to the access token",
607 | "protocol": "openid-connect",
608 | "attributes": {
609 | "include.in.token.scope": "false",
610 | "display.on.consent.screen": "true",
611 | "consent.screen.text": "${rolesScopeConsentText}"
612 | },
613 | "protocolMappers": [
614 | {
615 | "id": "94f73528-55ee-42e4-b62d-665ea7d8f637",
616 | "name": "realm roles",
617 | "protocol": "openid-connect",
618 | "protocolMapper": "oidc-usermodel-realm-role-mapper",
619 | "consentRequired": false,
620 | "config": {
621 | "user.attribute": "foo",
622 | "access.token.claim": "true",
623 | "claim.name": "realm_access.roles",
624 | "jsonType.label": "String",
625 | "multivalued": "true"
626 | }
627 | },
628 | {
629 | "id": "64d400e9-4ca9-48c2-b3b7-98048be0ca08",
630 | "name": "audience resolve",
631 | "protocol": "openid-connect",
632 | "protocolMapper": "oidc-audience-resolve-mapper",
633 | "consentRequired": false,
634 | "config": {}
635 | },
636 | {
637 | "id": "869f0374-0349-43c8-82a1-7fccb90e0027",
638 | "name": "client roles",
639 | "protocol": "openid-connect",
640 | "protocolMapper": "oidc-usermodel-client-role-mapper",
641 | "consentRequired": false,
642 | "config": {
643 | "user.attribute": "foo",
644 | "access.token.claim": "true",
645 | "claim.name": "resource_access.${client_id}.roles",
646 | "jsonType.label": "String",
647 | "multivalued": "true"
648 | }
649 | }
650 | ]
651 | },
652 | {
653 | "id": "c1e4540f-6360-4f6f-8cf5-f227c82e2347",
654 | "name": "microprofile-jwt",
655 | "description": "Microprofile - JWT built-in scope",
656 | "protocol": "openid-connect",
657 | "attributes": {
658 | "include.in.token.scope": "true",
659 | "display.on.consent.screen": "false"
660 | },
661 | "protocolMappers": [
662 | {
663 | "id": "f65c9ad2-d1dd-4e4a-befb-10805f26f331",
664 | "name": "groups",
665 | "protocol": "openid-connect",
666 | "protocolMapper": "oidc-usermodel-realm-role-mapper",
667 | "consentRequired": false,
668 | "config": {
669 | "multivalued": "true",
670 | "user.attribute": "foo",
671 | "id.token.claim": "true",
672 | "access.token.claim": "true",
673 | "claim.name": "groups",
674 | "jsonType.label": "String"
675 | }
676 | },
677 | {
678 | "id": "dbc22bba-3e86-4558-9179-06929f870065",
679 | "name": "upn",
680 | "protocol": "openid-connect",
681 | "protocolMapper": "oidc-usermodel-attribute-mapper",
682 | "consentRequired": false,
683 | "config": {
684 | "userinfo.token.claim": "true",
685 | "user.attribute": "username",
686 | "id.token.claim": "true",
687 | "access.token.claim": "true",
688 | "claim.name": "upn",
689 | "jsonType.label": "String"
690 | }
691 | }
692 | ]
693 | },
694 | {
695 | "id": "e31c4c42-d263-44ad-b999-41578169c0dc",
696 | "name": "profile",
697 | "description": "OpenID Connect built-in scope: profile",
698 | "protocol": "openid-connect",
699 | "attributes": {
700 | "include.in.token.scope": "true",
701 | "display.on.consent.screen": "true",
702 | "consent.screen.text": "${profileScopeConsentText}"
703 | },
704 | "protocolMappers": [
705 | {
706 | "id": "68c442cd-ad18-46f9-9d1b-5a58606b6bdf",
707 | "name": "profile",
708 | "protocol": "openid-connect",
709 | "protocolMapper": "oidc-usermodel-attribute-mapper",
710 | "consentRequired": false,
711 | "config": {
712 | "userinfo.token.claim": "true",
713 | "user.attribute": "profile",
714 | "id.token.claim": "true",
715 | "access.token.claim": "true",
716 | "claim.name": "profile",
717 | "jsonType.label": "String"
718 | }
719 | },
720 | {
721 | "id": "4e60bf75-e2fb-43ed-ad9c-a90e4a58f6a0",
722 | "name": "website",
723 | "protocol": "openid-connect",
724 | "protocolMapper": "oidc-usermodel-attribute-mapper",
725 | "consentRequired": false,
726 | "config": {
727 | "userinfo.token.claim": "true",
728 | "user.attribute": "website",
729 | "id.token.claim": "true",
730 | "access.token.claim": "true",
731 | "claim.name": "website",
732 | "jsonType.label": "String"
733 | }
734 | },
735 | {
736 | "id": "47a09725-3bf9-427a-8196-adfe4e3d3551",
737 | "name": "full name",
738 | "protocol": "openid-connect",
739 | "protocolMapper": "oidc-full-name-mapper",
740 | "consentRequired": false,
741 | "config": {
742 | "id.token.claim": "true",
743 | "access.token.claim": "true",
744 | "userinfo.token.claim": "true"
745 | }
746 | },
747 | {
748 | "id": "9a6f2d9c-9ead-488b-91db-8766e4e7d7d0",
749 | "name": "middle name",
750 | "protocol": "openid-connect",
751 | "protocolMapper": "oidc-usermodel-attribute-mapper",
752 | "consentRequired": false,
753 | "config": {
754 | "userinfo.token.claim": "true",
755 | "user.attribute": "middleName",
756 | "id.token.claim": "true",
757 | "access.token.claim": "true",
758 | "claim.name": "middle_name",
759 | "jsonType.label": "String"
760 | }
761 | },
762 | {
763 | "id": "e2092e09-72a9-4b6d-8a5d-d81b24524d04",
764 | "name": "gender",
765 | "protocol": "openid-connect",
766 | "protocolMapper": "oidc-usermodel-attribute-mapper",
767 | "consentRequired": false,
768 | "config": {
769 | "userinfo.token.claim": "true",
770 | "user.attribute": "gender",
771 | "id.token.claim": "true",
772 | "access.token.claim": "true",
773 | "claim.name": "gender",
774 | "jsonType.label": "String"
775 | }
776 | },
777 | {
778 | "id": "01814e18-7d30-4420-9efe-dd7b403e6e8c",
779 | "name": "family name",
780 | "protocol": "openid-connect",
781 | "protocolMapper": "oidc-usermodel-attribute-mapper",
782 | "consentRequired": false,
783 | "config": {
784 | "userinfo.token.claim": "true",
785 | "user.attribute": "lastName",
786 | "id.token.claim": "true",
787 | "access.token.claim": "true",
788 | "claim.name": "family_name",
789 | "jsonType.label": "String"
790 | }
791 | },
792 | {
793 | "id": "9fff3b34-cee7-400c-9282-507262a9ac0e",
794 | "name": "given name",
795 | "protocol": "openid-connect",
796 | "protocolMapper": "oidc-usermodel-attribute-mapper",
797 | "consentRequired": false,
798 | "config": {
799 | "userinfo.token.claim": "true",
800 | "user.attribute": "firstName",
801 | "id.token.claim": "true",
802 | "access.token.claim": "true",
803 | "claim.name": "given_name",
804 | "jsonType.label": "String"
805 | }
806 | },
807 | {
808 | "id": "c668358d-6b1b-433e-a1fe-1da138aac267",
809 | "name": "birthdate",
810 | "protocol": "openid-connect",
811 | "protocolMapper": "oidc-usermodel-attribute-mapper",
812 | "consentRequired": false,
813 | "config": {
814 | "userinfo.token.claim": "true",
815 | "user.attribute": "birthdate",
816 | "id.token.claim": "true",
817 | "access.token.claim": "true",
818 | "claim.name": "birthdate",
819 | "jsonType.label": "String"
820 | }
821 | },
822 | {
823 | "id": "0e5d8488-9799-4370-8176-0b7c6883f408",
824 | "name": "picture",
825 | "protocol": "openid-connect",
826 | "protocolMapper": "oidc-usermodel-attribute-mapper",
827 | "consentRequired": false,
828 | "config": {
829 | "userinfo.token.claim": "true",
830 | "user.attribute": "picture",
831 | "id.token.claim": "true",
832 | "access.token.claim": "true",
833 | "claim.name": "picture",
834 | "jsonType.label": "String"
835 | }
836 | },
837 | {
838 | "id": "912efed6-058b-4873-a7e0-b051976f0610",
839 | "name": "username",
840 | "protocol": "openid-connect",
841 | "protocolMapper": "oidc-usermodel-attribute-mapper",
842 | "consentRequired": false,
843 | "config": {
844 | "userinfo.token.claim": "true",
845 | "user.attribute": "username",
846 | "id.token.claim": "true",
847 | "access.token.claim": "true",
848 | "claim.name": "preferred_username",
849 | "jsonType.label": "String"
850 | }
851 | },
852 | {
853 | "id": "4237436e-cab0-4e23-8cf3-197809517d29",
854 | "name": "zoneinfo",
855 | "protocol": "openid-connect",
856 | "protocolMapper": "oidc-usermodel-attribute-mapper",
857 | "consentRequired": false,
858 | "config": {
859 | "userinfo.token.claim": "true",
860 | "user.attribute": "zoneinfo",
861 | "id.token.claim": "true",
862 | "access.token.claim": "true",
863 | "claim.name": "zoneinfo",
864 | "jsonType.label": "String"
865 | }
866 | },
867 | {
868 | "id": "763004da-39cd-4fe1-a739-027366e5c24e",
869 | "name": "locale",
870 | "protocol": "openid-connect",
871 | "protocolMapper": "oidc-usermodel-attribute-mapper",
872 | "consentRequired": false,
873 | "config": {
874 | "userinfo.token.claim": "true",
875 | "user.attribute": "locale",
876 | "id.token.claim": "true",
877 | "access.token.claim": "true",
878 | "claim.name": "locale",
879 | "jsonType.label": "String"
880 | }
881 | },
882 | {
883 | "id": "fdc6c54b-8b7a-496d-9292-14cf8a24aa66",
884 | "name": "nickname",
885 | "protocol": "openid-connect",
886 | "protocolMapper": "oidc-usermodel-attribute-mapper",
887 | "consentRequired": false,
888 | "config": {
889 | "userinfo.token.claim": "true",
890 | "user.attribute": "nickname",
891 | "id.token.claim": "true",
892 | "access.token.claim": "true",
893 | "claim.name": "nickname",
894 | "jsonType.label": "String"
895 | }
896 | },
897 | {
898 | "id": "0bab3185-3fe0-4efe-b29f-26acebe4b08a",
899 | "name": "updated at",
900 | "protocol": "openid-connect",
901 | "protocolMapper": "oidc-usermodel-attribute-mapper",
902 | "consentRequired": false,
903 | "config": {
904 | "userinfo.token.claim": "true",
905 | "user.attribute": "updatedAt",
906 | "id.token.claim": "true",
907 | "access.token.claim": "true",
908 | "claim.name": "updated_at",
909 | "jsonType.label": "long"
910 | }
911 | }
912 | ]
913 | }
914 | ],
915 | "defaultDefaultClientScopes": [
916 | "role_list",
917 | "profile",
918 | "email",
919 | "roles",
920 | "web-origins",
921 | "acr"
922 | ],
923 | "defaultOptionalClientScopes": [
924 | "offline_access",
925 | "address",
926 | "phone",
927 | "microprofile-jwt"
928 | ],
929 | "browserSecurityHeaders": {
930 | "contentSecurityPolicyReportOnly": "",
931 | "xContentTypeOptions": "nosniff",
932 | "referrerPolicy": "no-referrer",
933 | "xRobotsTag": "none",
934 | "xFrameOptions": "SAMEORIGIN",
935 | "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
936 | "xXSSProtection": "1; mode=block",
937 | "strictTransportSecurity": "max-age=31536000; includeSubDomains"
938 | },
939 | "smtpServer": {},
940 | "eventsEnabled": false,
941 | "eventsListeners": [
942 | "jboss-logging"
943 | ],
944 | "enabledEventTypes": [],
945 | "adminEventsEnabled": false,
946 | "adminEventsDetailsEnabled": false,
947 | "identityProviders": [],
948 | "identityProviderMappers": [],
949 | "components": {
950 | "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
951 | {
952 | "id": "48899fd6-2798-4155-87dd-7ac2c6c2f72f",
953 | "name": "Allowed Client Scopes",
954 | "providerId": "allowed-client-templates",
955 | "subType": "authenticated",
956 | "subComponents": {},
957 | "config": {
958 | "allow-default-scopes": [
959 | "true"
960 | ]
961 | }
962 | },
963 | {
964 | "id": "77dfaff3-bc24-489a-8485-c9528dca999c",
965 | "name": "Allowed Protocol Mapper Types",
966 | "providerId": "allowed-protocol-mappers",
967 | "subType": "anonymous",
968 | "subComponents": {},
969 | "config": {
970 | "allowed-protocol-mapper-types": [
971 | "saml-role-list-mapper",
972 | "saml-user-property-mapper",
973 | "oidc-full-name-mapper",
974 | "oidc-address-mapper",
975 | "oidc-usermodel-attribute-mapper",
976 | "oidc-sha256-pairwise-sub-mapper",
977 | "oidc-usermodel-property-mapper",
978 | "saml-user-attribute-mapper"
979 | ]
980 | }
981 | },
982 | {
983 | "id": "ba87f7eb-9c2f-4f75-8f8c-db11fdb74eaa",
984 | "name": "Full Scope Disabled",
985 | "providerId": "scope",
986 | "subType": "anonymous",
987 | "subComponents": {},
988 | "config": {}
989 | },
990 | {
991 | "id": "981d1165-bd59-421e-9589-b6dc81d6f87b",
992 | "name": "Consent Required",
993 | "providerId": "consent-required",
994 | "subType": "anonymous",
995 | "subComponents": {},
996 | "config": {}
997 | },
998 | {
999 | "id": "1d3f4d16-ad48-4de9-8957-574575a487b6",
1000 | "name": "Max Clients Limit",
1001 | "providerId": "max-clients",
1002 | "subType": "anonymous",
1003 | "subComponents": {},
1004 | "config": {
1005 | "max-clients": [
1006 | "200"
1007 | ]
1008 | }
1009 | },
1010 | {
1011 | "id": "ffb888eb-e9d0-4549-9c15-a9cfe5ee8ce3",
1012 | "name": "Allowed Client Scopes",
1013 | "providerId": "allowed-client-templates",
1014 | "subType": "anonymous",
1015 | "subComponents": {},
1016 | "config": {
1017 | "allow-default-scopes": [
1018 | "true"
1019 | ]
1020 | }
1021 | },
1022 | {
1023 | "id": "47984ad9-3db9-4d8d-8c99-e8569bfb113c",
1024 | "name": "Allowed Protocol Mapper Types",
1025 | "providerId": "allowed-protocol-mappers",
1026 | "subType": "authenticated",
1027 | "subComponents": {},
1028 | "config": {
1029 | "allowed-protocol-mapper-types": [
1030 | "saml-user-attribute-mapper",
1031 | "oidc-usermodel-property-mapper",
1032 | "saml-role-list-mapper",
1033 | "oidc-full-name-mapper",
1034 | "oidc-sha256-pairwise-sub-mapper",
1035 | "oidc-usermodel-attribute-mapper",
1036 | "oidc-address-mapper",
1037 | "saml-user-property-mapper"
1038 | ]
1039 | }
1040 | },
1041 | {
1042 | "id": "8e240cdb-a355-4fa5-91c4-7ec5cd515d69",
1043 | "name": "Trusted Hosts",
1044 | "providerId": "trusted-hosts",
1045 | "subType": "anonymous",
1046 | "subComponents": {},
1047 | "config": {
1048 | "host-sending-registration-request-must-match": [
1049 | "true"
1050 | ],
1051 | "client-uris-must-match": [
1052 | "true"
1053 | ]
1054 | }
1055 | }
1056 | ],
1057 | "org.keycloak.keys.KeyProvider": [
1058 | {
1059 | "id": "0d10601a-b4d4-4583-bd68-225207abc244",
1060 | "name": "hmac-generated",
1061 | "providerId": "hmac-generated",
1062 | "subComponents": {},
1063 | "config": {
1064 | "priority": [
1065 | "100"
1066 | ],
1067 | "algorithm": [
1068 | "HS256"
1069 | ]
1070 | }
1071 | },
1072 | {
1073 | "id": "3cb34b93-0d5f-48c3-89f6-23c6db611073",
1074 | "name": "aes-generated",
1075 | "providerId": "aes-generated",
1076 | "subComponents": {},
1077 | "config": {
1078 | "priority": [
1079 | "100"
1080 | ]
1081 | }
1082 | },
1083 | {
1084 | "id": "d708af13-78e3-4ca8-960f-dee9b13ad8f1",
1085 | "name": "rsa-enc-generated",
1086 | "providerId": "rsa-enc-generated",
1087 | "subComponents": {},
1088 | "config": {
1089 | "priority": [
1090 | "100"
1091 | ],
1092 | "algorithm": [
1093 | "RSA-OAEP"
1094 | ]
1095 | }
1096 | },
1097 | {
1098 | "id": "96aeefca-ccb4-40f5-bdae-6c21c41f64c0",
1099 | "name": "rsa-generated",
1100 | "providerId": "rsa-generated",
1101 | "subComponents": {},
1102 | "config": {
1103 | "priority": [
1104 | "100"
1105 | ]
1106 | }
1107 | }
1108 | ]
1109 | },
1110 | "internationalizationEnabled": false,
1111 | "supportedLocales": [],
1112 | "authenticationFlows": [
1113 | {
1114 | "id": "88635639-b59c-4f3f-b8d8-49a67faa2a85",
1115 | "alias": "Account verification options",
1116 | "description": "Method with which to verity the existing account",
1117 | "providerId": "basic-flow",
1118 | "topLevel": false,
1119 | "builtIn": true,
1120 | "authenticationExecutions": [
1121 | {
1122 | "authenticator": "idp-email-verification",
1123 | "authenticatorFlow": false,
1124 | "requirement": "ALTERNATIVE",
1125 | "priority": 10,
1126 | "autheticatorFlow": false,
1127 | "userSetupAllowed": false
1128 | },
1129 | {
1130 | "authenticatorFlow": true,
1131 | "requirement": "ALTERNATIVE",
1132 | "priority": 20,
1133 | "autheticatorFlow": true,
1134 | "flowAlias": "Verify Existing Account by Re-authentication",
1135 | "userSetupAllowed": false
1136 | }
1137 | ]
1138 | },
1139 | {
1140 | "id": "c2c32425-5fdc-418f-bcf2-49c8e4723bb1",
1141 | "alias": "Browser - Conditional OTP",
1142 | "description": "Flow to determine if the OTP is required for the authentication",
1143 | "providerId": "basic-flow",
1144 | "topLevel": false,
1145 | "builtIn": true,
1146 | "authenticationExecutions": [
1147 | {
1148 | "authenticator": "conditional-user-configured",
1149 | "authenticatorFlow": false,
1150 | "requirement": "REQUIRED",
1151 | "priority": 10,
1152 | "autheticatorFlow": false,
1153 | "userSetupAllowed": false
1154 | },
1155 | {
1156 | "authenticator": "auth-otp-form",
1157 | "authenticatorFlow": false,
1158 | "requirement": "REQUIRED",
1159 | "priority": 20,
1160 | "autheticatorFlow": false,
1161 | "userSetupAllowed": false
1162 | }
1163 | ]
1164 | },
1165 | {
1166 | "id": "f67cafc8-d118-4824-9cc5-a704f6e53d32",
1167 | "alias": "Browser with user attribute",
1168 | "description": "browser based authentication",
1169 | "providerId": "basic-flow",
1170 | "topLevel": true,
1171 | "builtIn": false,
1172 | "authenticationExecutions": [
1173 | {
1174 | "authenticator": "auth-cookie",
1175 | "authenticatorFlow": false,
1176 | "requirement": "ALTERNATIVE",
1177 | "priority": 10,
1178 | "autheticatorFlow": false,
1179 | "userSetupAllowed": false
1180 | },
1181 | {
1182 | "authenticator": "auth-spnego",
1183 | "authenticatorFlow": false,
1184 | "requirement": "DISABLED",
1185 | "priority": 20,
1186 | "autheticatorFlow": false,
1187 | "userSetupAllowed": false
1188 | },
1189 | {
1190 | "authenticator": "identity-provider-redirector",
1191 | "authenticatorFlow": false,
1192 | "requirement": "ALTERNATIVE",
1193 | "priority": 25,
1194 | "autheticatorFlow": false,
1195 | "userSetupAllowed": false
1196 | },
1197 | {
1198 | "authenticatorFlow": true,
1199 | "requirement": "ALTERNATIVE",
1200 | "priority": 30,
1201 | "autheticatorFlow": true,
1202 | "flowAlias": "Browser with user attribute forms",
1203 | "userSetupAllowed": false
1204 | }
1205 | ]
1206 | },
1207 | {
1208 | "id": "4810d943-bba2-4796-8883-b4347fcd5fe9",
1209 | "alias": "Browser with user attribute Browser - Conditional OTP",
1210 | "description": "Flow to determine if the OTP is required for the authentication",
1211 | "providerId": "basic-flow",
1212 | "topLevel": false,
1213 | "builtIn": false,
1214 | "authenticationExecutions": [
1215 | {
1216 | "authenticator": "conditional-user-configured",
1217 | "authenticatorFlow": false,
1218 | "requirement": "REQUIRED",
1219 | "priority": 10,
1220 | "autheticatorFlow": false,
1221 | "userSetupAllowed": false
1222 | },
1223 | {
1224 | "authenticator": "auth-otp-form",
1225 | "authenticatorFlow": false,
1226 | "requirement": "REQUIRED",
1227 | "priority": 20,
1228 | "autheticatorFlow": false,
1229 | "userSetupAllowed": false
1230 | }
1231 | ]
1232 | },
1233 | {
1234 | "id": "be489eea-ff3b-4970-bd95-e01edfdcf6ca",
1235 | "alias": "Browser with user attribute forms",
1236 | "description": "Username, password, otp and other auth forms.",
1237 | "providerId": "basic-flow",
1238 | "topLevel": false,
1239 | "builtIn": false,
1240 | "authenticationExecutions": [
1241 | {
1242 | "authenticatorConfig": "username password favorite number form config",
1243 | "authenticator": "auth-username-password-attr-form",
1244 | "authenticatorFlow": false,
1245 | "requirement": "REQUIRED",
1246 | "priority": 10,
1247 | "autheticatorFlow": false,
1248 | "userSetupAllowed": false
1249 | },
1250 | {
1251 | "authenticatorFlow": true,
1252 | "requirement": "CONDITIONAL",
1253 | "priority": 21,
1254 | "autheticatorFlow": true,
1255 | "flowAlias": "Browser with user attribute Browser - Conditional OTP",
1256 | "userSetupAllowed": false
1257 | }
1258 | ]
1259 | },
1260 | {
1261 | "id": "f1c208f3-c3fa-4237-be78-774e698b379d",
1262 | "alias": "Direct Grant - Conditional OTP",
1263 | "description": "Flow to determine if the OTP is required for the authentication",
1264 | "providerId": "basic-flow",
1265 | "topLevel": false,
1266 | "builtIn": true,
1267 | "authenticationExecutions": [
1268 | {
1269 | "authenticator": "conditional-user-configured",
1270 | "authenticatorFlow": false,
1271 | "requirement": "REQUIRED",
1272 | "priority": 10,
1273 | "autheticatorFlow": false,
1274 | "userSetupAllowed": false
1275 | },
1276 | {
1277 | "authenticator": "direct-grant-validate-otp",
1278 | "authenticatorFlow": false,
1279 | "requirement": "REQUIRED",
1280 | "priority": 20,
1281 | "autheticatorFlow": false,
1282 | "userSetupAllowed": false
1283 | }
1284 | ]
1285 | },
1286 | {
1287 | "id": "f1e9aeac-46b2-4ed2-b362-5f4a651d522b",
1288 | "alias": "First broker login - Conditional OTP",
1289 | "description": "Flow to determine if the OTP is required for the authentication",
1290 | "providerId": "basic-flow",
1291 | "topLevel": false,
1292 | "builtIn": true,
1293 | "authenticationExecutions": [
1294 | {
1295 | "authenticator": "conditional-user-configured",
1296 | "authenticatorFlow": false,
1297 | "requirement": "REQUIRED",
1298 | "priority": 10,
1299 | "autheticatorFlow": false,
1300 | "userSetupAllowed": false
1301 | },
1302 | {
1303 | "authenticator": "auth-otp-form",
1304 | "authenticatorFlow": false,
1305 | "requirement": "REQUIRED",
1306 | "priority": 20,
1307 | "autheticatorFlow": false,
1308 | "userSetupAllowed": false
1309 | }
1310 | ]
1311 | },
1312 | {
1313 | "id": "891b8796-4a38-4189-b462-d4976b7347b4",
1314 | "alias": "Handle Existing Account",
1315 | "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
1316 | "providerId": "basic-flow",
1317 | "topLevel": false,
1318 | "builtIn": true,
1319 | "authenticationExecutions": [
1320 | {
1321 | "authenticator": "idp-confirm-link",
1322 | "authenticatorFlow": false,
1323 | "requirement": "REQUIRED",
1324 | "priority": 10,
1325 | "autheticatorFlow": false,
1326 | "userSetupAllowed": false
1327 | },
1328 | {
1329 | "authenticatorFlow": true,
1330 | "requirement": "REQUIRED",
1331 | "priority": 20,
1332 | "autheticatorFlow": true,
1333 | "flowAlias": "Account verification options",
1334 | "userSetupAllowed": false
1335 | }
1336 | ]
1337 | },
1338 | {
1339 | "id": "684a0d69-07f6-4cc2-9e40-38b283fa1093",
1340 | "alias": "Reset - Conditional OTP",
1341 | "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
1342 | "providerId": "basic-flow",
1343 | "topLevel": false,
1344 | "builtIn": true,
1345 | "authenticationExecutions": [
1346 | {
1347 | "authenticator": "conditional-user-configured",
1348 | "authenticatorFlow": false,
1349 | "requirement": "REQUIRED",
1350 | "priority": 10,
1351 | "autheticatorFlow": false,
1352 | "userSetupAllowed": false
1353 | },
1354 | {
1355 | "authenticator": "reset-otp",
1356 | "authenticatorFlow": false,
1357 | "requirement": "REQUIRED",
1358 | "priority": 20,
1359 | "autheticatorFlow": false,
1360 | "userSetupAllowed": false
1361 | }
1362 | ]
1363 | },
1364 | {
1365 | "id": "fd028343-56cb-455c-9325-a363909edf5d",
1366 | "alias": "User creation or linking",
1367 | "description": "Flow for the existing/non-existing user alternatives",
1368 | "providerId": "basic-flow",
1369 | "topLevel": false,
1370 | "builtIn": true,
1371 | "authenticationExecutions": [
1372 | {
1373 | "authenticatorConfig": "create unique user config",
1374 | "authenticator": "idp-create-user-if-unique",
1375 | "authenticatorFlow": false,
1376 | "requirement": "ALTERNATIVE",
1377 | "priority": 10,
1378 | "autheticatorFlow": false,
1379 | "userSetupAllowed": false
1380 | },
1381 | {
1382 | "authenticatorFlow": true,
1383 | "requirement": "ALTERNATIVE",
1384 | "priority": 20,
1385 | "autheticatorFlow": true,
1386 | "flowAlias": "Handle Existing Account",
1387 | "userSetupAllowed": false
1388 | }
1389 | ]
1390 | },
1391 | {
1392 | "id": "100d0271-3c54-4ba1-9a37-b0078a82470c",
1393 | "alias": "Verify Existing Account by Re-authentication",
1394 | "description": "Reauthentication of existing account",
1395 | "providerId": "basic-flow",
1396 | "topLevel": false,
1397 | "builtIn": true,
1398 | "authenticationExecutions": [
1399 | {
1400 | "authenticator": "idp-username-password-form",
1401 | "authenticatorFlow": false,
1402 | "requirement": "REQUIRED",
1403 | "priority": 10,
1404 | "autheticatorFlow": false,
1405 | "userSetupAllowed": false
1406 | },
1407 | {
1408 | "authenticatorFlow": true,
1409 | "requirement": "CONDITIONAL",
1410 | "priority": 20,
1411 | "autheticatorFlow": true,
1412 | "flowAlias": "First broker login - Conditional OTP",
1413 | "userSetupAllowed": false
1414 | }
1415 | ]
1416 | },
1417 | {
1418 | "id": "05e49cca-3eab-48db-b282-414ed61e37f9",
1419 | "alias": "browser",
1420 | "description": "browser based authentication",
1421 | "providerId": "basic-flow",
1422 | "topLevel": true,
1423 | "builtIn": true,
1424 | "authenticationExecutions": [
1425 | {
1426 | "authenticator": "auth-cookie",
1427 | "authenticatorFlow": false,
1428 | "requirement": "ALTERNATIVE",
1429 | "priority": 10,
1430 | "autheticatorFlow": false,
1431 | "userSetupAllowed": false
1432 | },
1433 | {
1434 | "authenticator": "auth-spnego",
1435 | "authenticatorFlow": false,
1436 | "requirement": "DISABLED",
1437 | "priority": 20,
1438 | "autheticatorFlow": false,
1439 | "userSetupAllowed": false
1440 | },
1441 | {
1442 | "authenticator": "identity-provider-redirector",
1443 | "authenticatorFlow": false,
1444 | "requirement": "ALTERNATIVE",
1445 | "priority": 25,
1446 | "autheticatorFlow": false,
1447 | "userSetupAllowed": false
1448 | },
1449 | {
1450 | "authenticatorFlow": true,
1451 | "requirement": "ALTERNATIVE",
1452 | "priority": 30,
1453 | "autheticatorFlow": true,
1454 | "flowAlias": "forms",
1455 | "userSetupAllowed": false
1456 | }
1457 | ]
1458 | },
1459 | {
1460 | "id": "999d9553-e82b-4c06-918c-e82b4544e829",
1461 | "alias": "clients",
1462 | "description": "Base authentication for clients",
1463 | "providerId": "client-flow",
1464 | "topLevel": true,
1465 | "builtIn": true,
1466 | "authenticationExecutions": [
1467 | {
1468 | "authenticator": "client-secret",
1469 | "authenticatorFlow": false,
1470 | "requirement": "ALTERNATIVE",
1471 | "priority": 10,
1472 | "autheticatorFlow": false,
1473 | "userSetupAllowed": false
1474 | },
1475 | {
1476 | "authenticator": "client-jwt",
1477 | "authenticatorFlow": false,
1478 | "requirement": "ALTERNATIVE",
1479 | "priority": 20,
1480 | "autheticatorFlow": false,
1481 | "userSetupAllowed": false
1482 | },
1483 | {
1484 | "authenticator": "client-secret-jwt",
1485 | "authenticatorFlow": false,
1486 | "requirement": "ALTERNATIVE",
1487 | "priority": 30,
1488 | "autheticatorFlow": false,
1489 | "userSetupAllowed": false
1490 | },
1491 | {
1492 | "authenticator": "client-x509",
1493 | "authenticatorFlow": false,
1494 | "requirement": "ALTERNATIVE",
1495 | "priority": 40,
1496 | "autheticatorFlow": false,
1497 | "userSetupAllowed": false
1498 | }
1499 | ]
1500 | },
1501 | {
1502 | "id": "7c4c1b16-5c7f-49d8-9603-4c277cdab76a",
1503 | "alias": "direct grant",
1504 | "description": "OpenID Connect Resource Owner Grant",
1505 | "providerId": "basic-flow",
1506 | "topLevel": true,
1507 | "builtIn": true,
1508 | "authenticationExecutions": [
1509 | {
1510 | "authenticator": "direct-grant-validate-username",
1511 | "authenticatorFlow": false,
1512 | "requirement": "REQUIRED",
1513 | "priority": 10,
1514 | "autheticatorFlow": false,
1515 | "userSetupAllowed": false
1516 | },
1517 | {
1518 | "authenticator": "direct-grant-validate-password",
1519 | "authenticatorFlow": false,
1520 | "requirement": "REQUIRED",
1521 | "priority": 20,
1522 | "autheticatorFlow": false,
1523 | "userSetupAllowed": false
1524 | },
1525 | {
1526 | "authenticatorFlow": true,
1527 | "requirement": "CONDITIONAL",
1528 | "priority": 30,
1529 | "autheticatorFlow": true,
1530 | "flowAlias": "Direct Grant - Conditional OTP",
1531 | "userSetupAllowed": false
1532 | }
1533 | ]
1534 | },
1535 | {
1536 | "id": "b8db3d11-2123-4a26-add1-bc9ea269998d",
1537 | "alias": "docker auth",
1538 | "description": "Used by Docker clients to authenticate against the IDP",
1539 | "providerId": "basic-flow",
1540 | "topLevel": true,
1541 | "builtIn": true,
1542 | "authenticationExecutions": [
1543 | {
1544 | "authenticator": "docker-http-basic-authenticator",
1545 | "authenticatorFlow": false,
1546 | "requirement": "REQUIRED",
1547 | "priority": 10,
1548 | "autheticatorFlow": false,
1549 | "userSetupAllowed": false
1550 | }
1551 | ]
1552 | },
1553 | {
1554 | "id": "5d1329c6-23d2-4e14-83fb-bf5c98046a7a",
1555 | "alias": "first broker login",
1556 | "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
1557 | "providerId": "basic-flow",
1558 | "topLevel": true,
1559 | "builtIn": true,
1560 | "authenticationExecutions": [
1561 | {
1562 | "authenticatorConfig": "review profile config",
1563 | "authenticator": "idp-review-profile",
1564 | "authenticatorFlow": false,
1565 | "requirement": "REQUIRED",
1566 | "priority": 10,
1567 | "autheticatorFlow": false,
1568 | "userSetupAllowed": false
1569 | },
1570 | {
1571 | "authenticatorFlow": true,
1572 | "requirement": "REQUIRED",
1573 | "priority": 20,
1574 | "autheticatorFlow": true,
1575 | "flowAlias": "User creation or linking",
1576 | "userSetupAllowed": false
1577 | }
1578 | ]
1579 | },
1580 | {
1581 | "id": "f28eceb4-1666-439c-b3f7-a4b27d66156d",
1582 | "alias": "forms",
1583 | "description": "Username, password, otp and other auth forms.",
1584 | "providerId": "basic-flow",
1585 | "topLevel": false,
1586 | "builtIn": true,
1587 | "authenticationExecutions": [
1588 | {
1589 | "authenticator": "auth-username-password-form",
1590 | "authenticatorFlow": false,
1591 | "requirement": "REQUIRED",
1592 | "priority": 10,
1593 | "autheticatorFlow": false,
1594 | "userSetupAllowed": false
1595 | },
1596 | {
1597 | "authenticatorFlow": true,
1598 | "requirement": "CONDITIONAL",
1599 | "priority": 20,
1600 | "autheticatorFlow": true,
1601 | "flowAlias": "Browser - Conditional OTP",
1602 | "userSetupAllowed": false
1603 | }
1604 | ]
1605 | },
1606 | {
1607 | "id": "a1983ca1-0bd5-455f-9213-d4adfdc7be36",
1608 | "alias": "registration",
1609 | "description": "registration flow",
1610 | "providerId": "basic-flow",
1611 | "topLevel": true,
1612 | "builtIn": true,
1613 | "authenticationExecutions": [
1614 | {
1615 | "authenticator": "registration-page-form",
1616 | "authenticatorFlow": true,
1617 | "requirement": "REQUIRED",
1618 | "priority": 10,
1619 | "autheticatorFlow": true,
1620 | "flowAlias": "registration form",
1621 | "userSetupAllowed": false
1622 | }
1623 | ]
1624 | },
1625 | {
1626 | "id": "ed976ba1-2643-4a0d-9459-02a4ea707535",
1627 | "alias": "registration form",
1628 | "description": "registration form",
1629 | "providerId": "form-flow",
1630 | "topLevel": false,
1631 | "builtIn": true,
1632 | "authenticationExecutions": [
1633 | {
1634 | "authenticator": "registration-user-creation",
1635 | "authenticatorFlow": false,
1636 | "requirement": "REQUIRED",
1637 | "priority": 20,
1638 | "autheticatorFlow": false,
1639 | "userSetupAllowed": false
1640 | },
1641 | {
1642 | "authenticator": "registration-profile-action",
1643 | "authenticatorFlow": false,
1644 | "requirement": "REQUIRED",
1645 | "priority": 40,
1646 | "autheticatorFlow": false,
1647 | "userSetupAllowed": false
1648 | },
1649 | {
1650 | "authenticator": "registration-password-action",
1651 | "authenticatorFlow": false,
1652 | "requirement": "REQUIRED",
1653 | "priority": 50,
1654 | "autheticatorFlow": false,
1655 | "userSetupAllowed": false
1656 | },
1657 | {
1658 | "authenticator": "registration-recaptcha-action",
1659 | "authenticatorFlow": false,
1660 | "requirement": "DISABLED",
1661 | "priority": 60,
1662 | "autheticatorFlow": false,
1663 | "userSetupAllowed": false
1664 | }
1665 | ]
1666 | },
1667 | {
1668 | "id": "1c016b4a-acdf-41a8-a02b-124e5bb83768",
1669 | "alias": "reset credentials",
1670 | "description": "Reset credentials for a user if they forgot their password or something",
1671 | "providerId": "basic-flow",
1672 | "topLevel": true,
1673 | "builtIn": true,
1674 | "authenticationExecutions": [
1675 | {
1676 | "authenticator": "reset-credentials-choose-user",
1677 | "authenticatorFlow": false,
1678 | "requirement": "REQUIRED",
1679 | "priority": 10,
1680 | "autheticatorFlow": false,
1681 | "userSetupAllowed": false
1682 | },
1683 | {
1684 | "authenticator": "reset-credential-email",
1685 | "authenticatorFlow": false,
1686 | "requirement": "REQUIRED",
1687 | "priority": 20,
1688 | "autheticatorFlow": false,
1689 | "userSetupAllowed": false
1690 | },
1691 | {
1692 | "authenticator": "reset-password",
1693 | "authenticatorFlow": false,
1694 | "requirement": "REQUIRED",
1695 | "priority": 30,
1696 | "autheticatorFlow": false,
1697 | "userSetupAllowed": false
1698 | },
1699 | {
1700 | "authenticatorFlow": true,
1701 | "requirement": "CONDITIONAL",
1702 | "priority": 40,
1703 | "autheticatorFlow": true,
1704 | "flowAlias": "Reset - Conditional OTP",
1705 | "userSetupAllowed": false
1706 | }
1707 | ]
1708 | },
1709 | {
1710 | "id": "eb3a81e5-2e3d-490c-8898-9cca7d387cd4",
1711 | "alias": "saml ecp",
1712 | "description": "SAML ECP Profile Authentication Flow",
1713 | "providerId": "basic-flow",
1714 | "topLevel": true,
1715 | "builtIn": true,
1716 | "authenticationExecutions": [
1717 | {
1718 | "authenticator": "http-basic-authenticator",
1719 | "authenticatorFlow": false,
1720 | "requirement": "REQUIRED",
1721 | "priority": 10,
1722 | "autheticatorFlow": false,
1723 | "userSetupAllowed": false
1724 | }
1725 | ]
1726 | }
1727 | ],
1728 | "authenticatorConfig": [
1729 | {
1730 | "id": "6d7f7e83-1ec2-4ddb-a8c3-55c5f27a96d5",
1731 | "alias": "create unique user config",
1732 | "config": {
1733 | "require.password.update.after.registration": "false"
1734 | }
1735 | },
1736 | {
1737 | "id": "fb542612-8bc7-43dd-8cc0-b74493100e67",
1738 | "alias": "review profile config",
1739 | "config": {
1740 | "update.profile.on.first.login": "missing"
1741 | }
1742 | },
1743 | {
1744 | "id": "7eb11994-09b3-4df2-807b-e1b1b5ce9cea",
1745 | "alias": "username password favorite number form config",
1746 | "config": {
1747 | "user_attribute": "favorite_number",
1748 | "generate_label": "true",
1749 | "user_attribute_label": ""
1750 | }
1751 | }
1752 | ],
1753 | "requiredActions": [
1754 | {
1755 | "alias": "CONFIGURE_TOTP",
1756 | "name": "Configure OTP",
1757 | "providerId": "CONFIGURE_TOTP",
1758 | "enabled": true,
1759 | "defaultAction": false,
1760 | "priority": 10,
1761 | "config": {}
1762 | },
1763 | {
1764 | "alias": "TERMS_AND_CONDITIONS",
1765 | "name": "Terms and Conditions",
1766 | "providerId": "TERMS_AND_CONDITIONS",
1767 | "enabled": false,
1768 | "defaultAction": false,
1769 | "priority": 20,
1770 | "config": {}
1771 | },
1772 | {
1773 | "alias": "UPDATE_PASSWORD",
1774 | "name": "Update Password",
1775 | "providerId": "UPDATE_PASSWORD",
1776 | "enabled": true,
1777 | "defaultAction": false,
1778 | "priority": 30,
1779 | "config": {}
1780 | },
1781 | {
1782 | "alias": "UPDATE_PROFILE",
1783 | "name": "Update Profile",
1784 | "providerId": "UPDATE_PROFILE",
1785 | "enabled": true,
1786 | "defaultAction": false,
1787 | "priority": 40,
1788 | "config": {}
1789 | },
1790 | {
1791 | "alias": "VERIFY_EMAIL",
1792 | "name": "Verify Email",
1793 | "providerId": "VERIFY_EMAIL",
1794 | "enabled": true,
1795 | "defaultAction": false,
1796 | "priority": 50,
1797 | "config": {}
1798 | },
1799 | {
1800 | "alias": "delete_account",
1801 | "name": "Delete Account",
1802 | "providerId": "delete_account",
1803 | "enabled": false,
1804 | "defaultAction": false,
1805 | "priority": 60,
1806 | "config": {}
1807 | },
1808 | {
1809 | "alias": "webauthn-register",
1810 | "name": "Webauthn Register",
1811 | "providerId": "webauthn-register",
1812 | "enabled": true,
1813 | "defaultAction": false,
1814 | "priority": 70,
1815 | "config": {}
1816 | },
1817 | {
1818 | "alias": "webauthn-register-passwordless",
1819 | "name": "Webauthn Register Passwordless",
1820 | "providerId": "webauthn-register-passwordless",
1821 | "enabled": true,
1822 | "defaultAction": false,
1823 | "priority": 80,
1824 | "config": {}
1825 | },
1826 | {
1827 | "alias": "update_user_locale",
1828 | "name": "Update User Locale",
1829 | "providerId": "update_user_locale",
1830 | "enabled": true,
1831 | "defaultAction": false,
1832 | "priority": 1000,
1833 | "config": {}
1834 | }
1835 | ],
1836 | "browserFlow": "browser",
1837 | "registrationFlow": "registration",
1838 | "directGrantFlow": "direct grant",
1839 | "resetCredentialsFlow": "reset credentials",
1840 | "clientAuthenticationFlow": "clients",
1841 | "dockerAuthenticationFlow": "docker auth",
1842 | "attributes": {
1843 | "cibaBackchannelTokenDeliveryMode": "poll",
1844 | "cibaExpiresIn": "120",
1845 | "cibaAuthRequestedUserHint": "login_hint",
1846 | "oauth2DeviceCodeLifespan": "600",
1847 | "oauth2DevicePollingInterval": "5",
1848 | "parRequestUriLifespan": "60",
1849 | "cibaInterval": "5",
1850 | "realmReusableOtpCode": "false"
1851 | },
1852 | "keycloakVersion": "22.0.3",
1853 | "userManagedAccessAllowed": false,
1854 | "clientProfiles": {
1855 | "profiles": []
1856 | },
1857 | "clientPolicies": {
1858 | "policies": []
1859 | },
1860 | "users" : [ {
1861 | "id" : "462c7cb4-e444-4eac-8d68-0686c30eb245",
1862 | "createdTimestamp" : 1621784842260,
1863 | "username" : "test",
1864 | "enabled" : true,
1865 | "totp" : false,
1866 | "emailVerified" : false,
1867 | "attributes" : {
1868 | "favorite_number" : [ "46" ]
1869 | },
1870 | "credentials" : [ {
1871 | "id" : "a0f53e85-a522-4ab4-85cb-95224dec8d35",
1872 | "type" : "password",
1873 | "createdDate" : 1621784863307,
1874 | "secretData" : "{\"value\":\"MFcHPyUSTiRqMhJum6z9KSIZbwJZAUswqq1zoVjoA6Cse8iylnjw9fOkEO72IWgS+PIj3RW7WB/CUp2deW8Swg==\",\"salt\":\"WztZBrFIbqNmEMGkQer7eQ==\",\"additionalParameters\":{}}",
1875 | "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
1876 | } ],
1877 | "requiredActions" : [ ],
1878 | "realmRoles" : [ "default-roles-dev-realm" ],
1879 | "notBefore" : 0,
1880 | "groups" : [ ]
1881 | } ]
1882 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | keycloak:
5 | container_name: keycloak
6 | image: quay.io/keycloak/keycloak:24.0.1
7 | entrypoint: [ "/opt/keycloak/bin/kc.sh", "--verbose", "start-dev", "--import-realm" ]
8 | environment:
9 | DEBUG: 'true'
10 | DEBUG_PORT: '*:8787'
11 | KC_PROXY: edge
12 | KC_HTTP_PORT: 8080
13 | KEYCLOAK_ADMIN: admin
14 | KEYCLOAK_ADMIN_PASSWORD: admin
15 | volumes:
16 | - type: bind
17 | source: ./target
18 | target: /opt/keycloak/providers
19 | - type: bind
20 | source: ./dev-realm.json
21 | target: /opt/keycloak/data/import/dev-realm.json
22 | ports:
23 | - '8080:8080'
24 | - '8787:8787'
25 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | Keycloak username password attribute authenticator
8 |
9 | keycloak-username-password-attribute-authenticator
10 | io.github.kilmajster
11 | SNAPSHOT
12 |
13 | jar
14 |
15 | Default Keycloak login form with additional user attribute validation
16 | https://github.com/kilmajster/keycloak-username-password-attribute-authenticator
17 | 2021
18 |
19 |
20 |
21 | MIT
22 | https://github.com/kilmajster/keycloak-username-password-attribute-authenticator/blob/main/LICENSE
23 | repo
24 |
25 |
26 |
27 |
28 | UTF-8
29 | 17
30 | 17
31 |
32 | 24.0.5
33 |
34 | 3.12.1
35 | 3.1.0
36 | 3.3.0
37 | 3.6.3
38 | 3.3.0
39 | 3.2.5
40 |
41 |
42 |
43 |
44 | org.keycloak
45 | keycloak-services
46 | ${keycloak.version}
47 | provided
48 |
49 |
50 | org.keycloak
51 | keycloak-server-spi
52 | ${keycloak.version}
53 | provided
54 |
55 |
56 | org.keycloak
57 | keycloak-server-spi-private
58 | ${keycloak.version}
59 | provided
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeForm.java:
--------------------------------------------------------------------------------
1 | package io.github.kilmajster.keycloak;
2 |
3 | import jakarta.ws.rs.core.MultivaluedMap;
4 | import jakarta.ws.rs.core.Response;
5 | import org.keycloak.authentication.AuthenticationFlowContext;
6 | import org.keycloak.authentication.AuthenticationFlowError;
7 | import org.keycloak.authentication.Authenticator;
8 | import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
9 | import org.keycloak.events.Details;
10 | import org.keycloak.events.Errors;
11 | import org.keycloak.forms.login.LoginFormsProvider;
12 | import org.keycloak.models.UserModel;
13 | import org.keycloak.models.utils.FormMessage;
14 | import org.keycloak.services.ServicesLogger;
15 |
16 | import static io.github.kilmajster.keycloak.UsernamePasswordAttributeFormConfiguration.*;
17 | import static org.keycloak.services.validation.Validation.FIELD_PASSWORD;
18 |
19 | public class UsernamePasswordAttributeForm extends UsernamePasswordForm implements Authenticator {
20 |
21 | protected static ServicesLogger log = ServicesLogger.LOGGER;
22 |
23 | @Override
24 | protected Response challenge(AuthenticationFlowContext context, String error, String field) {
25 | setUserAttributeFormLabel(context);
26 | setUserAttributeFormErrorMessage(context);
27 |
28 | return super.challenge(context, null, null);
29 | }
30 |
31 | @Override
32 | protected Response challenge(AuthenticationFlowContext context, MultivaluedMap
formData) {
33 | setUserAttributeFormLabel(context);
34 |
35 | return super.challenge(context, formData);
36 | }
37 |
38 | @Override
39 | protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap formData) {
40 | return super.validateForm(context, formData)
41 | && isUserAttributeValid(context, formData.getFirst(USER_ATTRIBUTE));
42 | }
43 |
44 | private void setUserAttributeFormLabel(AuthenticationFlowContext context) {
45 | String userAttributeLabel = configPropertyOf(context, USER_ATTRIBUTE_LABEL);
46 |
47 | // label
48 | if (userAttributeLabel != null) {
49 | context.form().setAttribute(USER_ATTRIBUTE_LABEL, userAttributeLabel);
50 | } else {
51 | String userAttributeName = configPropertyOf(context, USER_ATTRIBUTE);
52 | if (userAttributeName != null && !userAttributeName.isEmpty()) {
53 | context.form().setAttribute(
54 | USER_ATTRIBUTE_LABEL,
55 | isGenerateLabelEnabled(context)
56 | ? generateLabel(userAttributeName, true)
57 | : userAttributeName
58 | );
59 | } else {
60 | log.warn("Configuration of keycloak-user-attribute-authenticator is incomplete! " +
61 | "At least user_attribute property needs to be set!");
62 | }
63 | }
64 | }
65 |
66 | private void setUserAttributeFormErrorMessage(AuthenticationFlowContext context) {
67 | String userAttributeName = configPropertyOf(context, USER_ATTRIBUTE);
68 | String userAttributeErrorMessage = configPropertyOf(context, USER_ATTRIBUTE_ERROR_MESSAGE);
69 |
70 | if (userAttributeErrorMessage != null) {
71 | context.form().addError(
72 | new FormMessage(FIELD_PASSWORD, userAttributeErrorMessage, userAttributeName)
73 | );
74 | } else {
75 | if (userAttributeName != null && !userAttributeName.isEmpty()) {
76 | context.form().addError(
77 | new FormMessage(
78 | FIELD_PASSWORD,
79 | "invalidUsernamePasswordOrAttributeMessage",
80 | isGenerateLabelEnabled(context)
81 | ? generateLabel(userAttributeName, false)
82 | : userAttributeName
83 | )
84 | );
85 | } else {
86 | log.warn("Configuration of keycloak-user-attribute-authenticator is incomplete! " +
87 | "At least user_attribute property needs to be set!");
88 | }
89 | }
90 | }
91 |
92 | private boolean isUserAttributeValid(AuthenticationFlowContext context, String providedAttribute) {
93 | String attributeName = context.getAuthenticatorConfig().getConfig().get(USER_ATTRIBUTE);
94 | UserModel user = context.getUser();
95 | boolean attributeValid = user != null
96 | && user.getAttributeStream(attributeName).anyMatch(attr -> attr.equals(providedAttribute));
97 |
98 | return attributeValid || badAttributeHandler(context, user);
99 | }
100 |
101 | private boolean badAttributeHandler(AuthenticationFlowContext context, UserModel user) {
102 | context.getEvent().user(user);
103 | context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
104 | context.getEvent().detail(Details.REASON, "Invalid user attribute was provided");
105 |
106 | if (isUserAlreadySetBeforeUsernamePasswordAuth(context)) {
107 | LoginFormsProvider form = context.form();
108 | form.setAttribute(LoginFormsProvider.USERNAME_HIDDEN, true);
109 | form.setAttribute(LoginFormsProvider.REGISTRATION_DISABLED, true);
110 | }
111 |
112 | Response challengeResponse = challenge(context, getDefaultChallengeMessage(context), FIELD_PASSWORD);
113 | context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
114 | context.clearUser();
115 |
116 | return false;
117 | }
118 |
119 | private String generateLabel(final String attributeName, boolean capitalize) {
120 | final String lowercaseWithSpaces = attributeName
121 | .toLowerCase()
122 | .replace(".", " ")
123 | .replace("_", " ")
124 | .replace("-", " ");
125 |
126 | return capitalize ? capitalize(lowercaseWithSpaces) : lowercaseWithSpaces;
127 | }
128 |
129 | private String capitalize(final String toCapitalize) {
130 | return toCapitalize.substring(0, 1).toUpperCase() + toCapitalize.substring(1).toLowerCase();
131 | }
132 | }
--------------------------------------------------------------------------------
/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.github.kilmajster.keycloak;
2 |
3 | import org.keycloak.authentication.AuthenticationFlowContext;
4 | import org.keycloak.provider.ProviderConfigProperty;
5 | import org.keycloak.provider.ProviderConfigurationBuilder;
6 |
7 | import java.util.List;
8 | import java.util.Optional;
9 |
10 | public interface UsernamePasswordAttributeFormConfiguration {
11 |
12 | String USER_ATTRIBUTE = "user_attribute";
13 | String GENERATE_LABEL = "generate_label";
14 | String USER_ATTRIBUTE_LABEL = "user_attribute_label";
15 | String USER_ATTRIBUTE_ERROR_MESSAGE = "user_attribute_error_message";
16 |
17 | List PROPS = ProviderConfigurationBuilder.create()
18 |
19 | .property()
20 | .name(USER_ATTRIBUTE)
21 | .type(ProviderConfigProperty.STRING_TYPE)
22 | .label("User attribute")
23 | .helpText("Attribute used to validate login form.")
24 | .add()
25 |
26 | .property()
27 | .name(GENERATE_LABEL)
28 | .type(ProviderConfigProperty.BOOLEAN_TYPE)
29 | .label("Generate label")
30 | .defaultValue("true") // only string value is accepted
31 | .helpText("If enabled, label for login form will be generated based on attribute name, so attribute with name:" +
32 | " \"favorite_number\" will be labeled as \"Favorite number\", \"REALLY_custom.user-Attribute\" will be translated " +
33 | "to \"Really custom user attribute\", etc. By default, set to true. If User attribute form label " +
34 | "is configured, label is taken form configuration and generation is skipped.")
35 | .add()
36 |
37 | .property()
38 | .name(USER_ATTRIBUTE_LABEL)
39 | .type(ProviderConfigProperty.STRING_TYPE)
40 | .label("User attribute form label")
41 | .helpText("Message which will be displayed as user attribute input label. If value is a valid message key, then proper translation will be used.")
42 | .add()
43 |
44 | .property()
45 | .name(USER_ATTRIBUTE_ERROR_MESSAGE)
46 | .type(ProviderConfigProperty.STRING_TYPE)
47 | .label("Invalid user attribute error message")
48 | .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.")
49 | .add()
50 |
51 | .build();
52 |
53 | static String configPropertyOf(final AuthenticationFlowContext context, final String configPropertyName) {
54 | return Optional.ofNullable(System.getenv(configPropertyName.toUpperCase()))
55 | .orElse(context.getAuthenticatorConfig().getConfig().get(configPropertyName));
56 | }
57 |
58 | static boolean isGenerateLabelEnabled(final AuthenticationFlowContext context) {
59 | return Boolean.parseBoolean(configPropertyOf(context, GENERATE_LABEL));
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/io/github/kilmajster/keycloak/UsernamePasswordAttributeFormFactory.java:
--------------------------------------------------------------------------------
1 | package io.github.kilmajster.keycloak;
2 |
3 | import org.keycloak.Config;
4 | import org.keycloak.authentication.Authenticator;
5 | import org.keycloak.authentication.AuthenticatorFactory;
6 | import org.keycloak.models.AuthenticationExecutionModel;
7 | import org.keycloak.models.KeycloakSession;
8 | import org.keycloak.models.KeycloakSessionFactory;
9 | import org.keycloak.models.credential.PasswordCredentialModel;
10 | import org.keycloak.provider.ProviderConfigProperty;
11 |
12 | import java.util.List;
13 |
14 | public class UsernamePasswordAttributeFormFactory implements AuthenticatorFactory {
15 |
16 | public static final String PROVIDER_ID = "auth-username-password-attr-form";
17 | public static final UsernamePasswordAttributeForm SINGLETON = new UsernamePasswordAttributeForm();
18 |
19 | @Override
20 | public Authenticator create(KeycloakSession session) {
21 | return SINGLETON;
22 | }
23 |
24 | @Override
25 | public List getConfigProperties() {
26 | return UsernamePasswordAttributeFormConfiguration.PROPS;
27 | }
28 |
29 | @Override
30 | public void init(Config.Scope config) {
31 |
32 | }
33 |
34 | @Override
35 | public void postInit(KeycloakSessionFactory factory) {
36 |
37 | }
38 |
39 | @Override
40 | public void close() {
41 |
42 | }
43 |
44 | @Override
45 | public String getId() {
46 | return PROVIDER_ID;
47 | }
48 |
49 | @Override
50 | public String getReferenceCategory() {
51 | return PasswordCredentialModel.TYPE;
52 | }
53 |
54 | @Override
55 | public boolean isConfigurable() {
56 | return true;
57 | }
58 |
59 | public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
60 | AuthenticationExecutionModel.Requirement.REQUIRED,
61 | AuthenticationExecutionModel.Requirement.DISABLED,
62 | };
63 |
64 | @Override
65 | public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
66 | return REQUIREMENT_CHOICES;
67 | }
68 |
69 | @Override
70 | public String getDisplayType() {
71 | return "Username Password Attribute Form";
72 | }
73 |
74 | @Override
75 | public String getHelpText() {
76 | return "Validates a username, password and selected user attribute from login form.";
77 | }
78 |
79 | @Override
80 | public boolean isUserSetupAllowed() {
81 | return false;
82 | }
83 | }
--------------------------------------------------------------------------------
/src/main/resources/META-INF/keycloak-themes.json:
--------------------------------------------------------------------------------
1 | {
2 | "themes": [{
3 | "name": "base-with-attribute",
4 | "types": ["login"]
5 | }]
6 | }
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory:
--------------------------------------------------------------------------------
1 | io.github.kilmajster.keycloak.UsernamePasswordAttributeFormFactory
--------------------------------------------------------------------------------
/src/main/resources/theme/base-with-attribute/login/login.ftl:
--------------------------------------------------------------------------------
1 | <#import "template.ftl" as layout>
2 | <@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
3 | <#if section = "header">
4 | ${msg("loginAccountTitle")}
5 | <#elseif section = "form">
6 |
102 |
103 | <#elseif section = "info" >
104 | <#if realm.password && realm.registrationAllowed && !registrationDisabled??>
105 |
111 | #if>
112 | <#elseif section = "socialProviders" >
113 | <#if realm.password && social.providers??>
114 |
134 | #if>
135 | #if>
136 |
137 | @layout.registrationLayout>
--------------------------------------------------------------------------------
/src/main/resources/theme/base-with-attribute/login/messages/messages_en.properties:
--------------------------------------------------------------------------------
1 | defaultUserAttributeLabel=User attribute
2 | showUserAttribute=Show attribute
3 | hideUserAttribute=Hide attribute
4 | invalidUsernamePasswordOrAttributeMessage=Invalid username, password or {0}.
--------------------------------------------------------------------------------
/src/main/resources/theme/base-with-attribute/login/theme.properties:
--------------------------------------------------------------------------------
1 | parent=keycloak
2 | import=common/keycloak
--------------------------------------------------------------------------------