├── demoapp ├── run.sh └── index.html ├── etc ├── runDemoApp.sh ├── triggerDockerExtensionDeploy.sh └── exportRealm.sh ├── requests ├── acme.http └── http-client.env.json ├── acme-themes ├── src │ └── main │ │ └── resources │ │ └── theme │ │ └── acme │ │ └── login │ │ ├── resources │ │ ├── js │ │ │ └── acme-login.js │ │ └── css │ │ │ └── acme-login.css │ │ └── theme.properties └── pom.xml ├── slides └── 20210406-JUGSaar-keycloak-extension-development.pdf ├── acme-extensions ├── src │ ├── test │ │ ├── resources │ │ │ └── log4j.properties │ │ └── java │ │ │ └── demo │ │ │ └── acme │ │ │ └── keycloak │ │ │ ├── BoostrapTest.java │ │ │ ├── KeycloakTestSupport.java │ │ │ └── AcmeKeycloakIntegrationTest.java │ └── main │ │ ├── java │ │ └── demo │ │ │ └── acme │ │ │ └── keycloak │ │ │ ├── api │ │ │ ├── AcmeResourceProviderFactory.java │ │ │ ├── AcmeResource.java │ │ │ └── AcmeResourceProvider.java │ │ │ ├── audit │ │ │ ├── AcmeAuditListenerFactory.java │ │ │ └── AcmeAuditListener.java │ │ │ └── oidc │ │ │ └── AgeInfoMapper.java │ │ └── resources │ │ └── META-INF │ │ └── jboss-deployment-structure.xml └── pom.xml ├── acme-dist ├── src │ └── main │ │ └── docker │ │ └── keycloak │ │ └── Dockerfile └── pom.xml ├── docker-compose.yml ├── pom.xml ├── cli └── onstart-0001-init.cli ├── readme.md ├── notes.md ├── .gitignore └── imex └── acme-realm.json /demoapp/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python3 -m http.server 4000 -------------------------------------------------------------------------------- /etc/runDemoApp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | (cd demoapp; python3 -m http.server 4000) -------------------------------------------------------------------------------- /requests/acme.http: -------------------------------------------------------------------------------- 1 | ### Call ping resource as user 2 | GET {{KC_ISSUER_URL}}/acme-resources/ping 3 | Accept: application/json 4 | Authorization: Bearer {{testClientToken}} -------------------------------------------------------------------------------- /acme-themes/src/main/resources/theme/acme/login/resources/js/acme-login.js: -------------------------------------------------------------------------------- 1 | // acme-login.js 2 | 3 | (function onAcmeLogin() { 4 | console.log("acme login jugsaar"); 5 | })(); 6 | 7 | -------------------------------------------------------------------------------- /slides/20210406-JUGSaar-keycloak-extension-development.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasdarimont/keycloak-extensions-talk/HEAD/slides/20210406-JUGSaar-keycloak-extension-development.pdf -------------------------------------------------------------------------------- /etc/triggerDockerExtensionDeploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eou pipefail 4 | 5 | docker-compose exec -T keycloak \ 6 | touch /opt/jboss/keycloak/standalone/deployments/acme-extensions.jar.dodeploy -------------------------------------------------------------------------------- /acme-extensions/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO,stdout 2 | 3 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t[%t]\t%m%n -------------------------------------------------------------------------------- /acme-extensions/src/test/java/demo/acme/keycloak/BoostrapTest.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class BoostrapTest { 7 | 8 | @Test 9 | public void shouldRunAsUnitTest() { 10 | Assertions.assertTrue(true); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /acme-dist/src/main/docker/keycloak/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG KEYCLOAK_VERSION=12.0.4 2 | FROM quay.io/keycloak/keycloak:$KEYCLOAK_VERSION 3 | 4 | COPY --chown=jboss:jboss maven/cli/ /opt/jboss/startup-scripts 5 | COPY --chown=jboss:jboss maven/acme-extensions/ /opt/jboss/keycloak/standalone/deployments 6 | COPY --chown=jboss:jboss maven/acme-theme /opt/jboss/keycloak/themes/acme 7 | 8 | -------------------------------------------------------------------------------- /etc/exportRealm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eou pipefail 4 | 5 | docker-compose exec keycloak \ 6 | /opt/jboss/keycloak/bin/standalone.sh -c standalone.xml \ 7 | -Djboss.socket.binding.port-offset=10000 \ 8 | -Dkeycloak.migration.action=export \ 9 | -Dkeycloak.migration.file=/opt/jboss/imex/acme-realm.json \ 10 | -Dkeycloak.migration.provider=singleFile \ 11 | -Dkeycloak.migration.realmName=acme 12 | -------------------------------------------------------------------------------- /acme-themes/src/main/resources/theme/acme/login/theme.properties: -------------------------------------------------------------------------------- 1 | parent=keycloak 2 | import=common/keycloak 3 | # Custom Styles 4 | styles=css/login.css css/acme-login.css 5 | stylesCommon=web_modules/@patternfly/react-core/dist/styles/base.css web_modules/@patternfly/react-core/dist/styles/app.css node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css lib/pficon/pficon.css 6 | # Custom JavaScript 7 | scripts=js/acme-login.js 8 | # Custom Page Metadata 9 | meta=viewport==width=device-width,initial-scale=1 10 | -------------------------------------------------------------------------------- /acme-themes/src/main/resources/theme/acme/login/resources/css/acme-login.css: -------------------------------------------------------------------------------- 1 | /* white-login.css */ 2 | /* see: https://leaverou.github.io/css3patterns/ */ 3 | .login-pf body { 4 | background: radial-gradient(black 15%, transparent 16%) 0 0, 5 | radial-gradient(black 15%, transparent 16%) 8px 8px, 6 | radial-gradient(rgba(255, 255, 255, 0.1) 15%, transparent 20%) 0 1px, 7 | radial-gradient(rgba(255, 255, 255, 0.1) 15%, transparent 20%) 8px 9px !important; 8 | background-color: #282828 !important; 9 | background-size: 16px 16px !important; 10 | } 11 | -------------------------------------------------------------------------------- /acme-themes/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | keycloak-extensions-talk 7 | com.github.thomasdarimont.keycloak 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | acme-themes 13 | 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | keycloak: 4 | container_name: keycloak-extdev 5 | image: quay.io/keycloak/keycloak:12.0.4 6 | user: "1000:1000" 7 | environment: 8 | KEYCLOAK_USER: "admin" 9 | KEYCLOAK_PASSWORD: "admin" 10 | KEYCLOAK_THEME_CACHING: "false" 11 | KEYCLOAK_THEME_TEMPLATE_CACHING: "false" 12 | KEYCLOAK_IMPORT: "/opt/jboss/imex/acme-realm.json" 13 | command: [ "--debug", "*:8787", "--server-config", "standalone.xml" ] 14 | ports: 15 | - "8080:8080" 16 | - "127.0.0.1:8787:8787" 17 | volumes: 18 | - ./acme-extensions/target/classes:/opt/jboss/keycloak/standalone/deployments/acme-extensions.jar:z 19 | - ./acme-themes/target/classes/theme/acme:/opt/jboss/keycloak/themes/acme:z 20 | - ./testrun/data:/opt/jboss/keycloak/standalone/data:z 21 | - ./imex:/opt/jboss/imex:z 22 | - ./cli:/opt/jboss/startup-scripts:z 23 | -------------------------------------------------------------------------------- /acme-extensions/src/main/java/demo/acme/keycloak/api/AcmeResourceProviderFactory.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak.api; 2 | 3 | import com.google.auto.service.AutoService; 4 | import org.keycloak.Config; 5 | import org.keycloak.models.KeycloakSession; 6 | import org.keycloak.models.KeycloakSessionFactory; 7 | import org.keycloak.services.resource.RealmResourceProvider; 8 | import org.keycloak.services.resource.RealmResourceProviderFactory; 9 | 10 | @AutoService(RealmResourceProviderFactory.class) 11 | public class AcmeResourceProviderFactory implements RealmResourceProviderFactory { 12 | 13 | @Override 14 | public String getId() { 15 | return AcmeResourceProvider.ID; 16 | } 17 | 18 | @Override 19 | public RealmResourceProvider create(KeycloakSession session) { 20 | return new AcmeResourceProvider(session); 21 | } 22 | 23 | @Override 24 | public void init(Config.Scope config) { 25 | // NOOP 26 | } 27 | 28 | @Override 29 | public void postInit(KeycloakSessionFactory factory) { 30 | // NOOP 31 | } 32 | 33 | @Override 34 | public void close() { 35 | // NOOP 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /requests/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "dev": { 3 | "KC_ISSUER_URL": "http://localhost:8080/auth/realms/acme", 4 | "testClientToken": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI5enFRZTA3cm9MMU1OUUdxdk5wYmw1MVZhb0EtWnBXV2JlaTAwX2s2NDJrIn0.eyJleHAiOjE2MTc2NjY1NDIsImlhdCI6MTYxNzY1NTc0MiwianRpIjoiM2EwZDdlZTQtMjQwZS00YjJkLTk2ZGMtZDhhYjJmMDJhNGNkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2FjbWUiLCJzdWIiOiJlYTZjMWE4ZC04ZTI3LTRlZDMtYmY0OC01NzdkMjY0YjdkNTkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0LWNsaWVudCIsInNlc3Npb25fc3RhdGUiOiIwNTVkMmQ4Mi00NzdhLTQzYzUtYTRiOS00ZDdiZWQyYmNhZGUiLCJhY3IiOiIxIiwic2NvcGUiOiJvcGVuaWQgZW1haWwgYWNtZS5hZ2VpbmZvIGFjbWUucHJvZmlsZSBhY21lLmFwaSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkZpcnN0bmFtZSBMYXN0bmFtZSIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QtdXNlci1hZ2UyMiIsImdpdmVuX25hbWUiOiJGaXJzdG5hbWUiLCJmYW1pbHlfbmFtZSI6Ikxhc3RuYW1lIn0.EJEalMMP_dVTqlWyMmWm6kpSeD4hOQd9QZj32rgryO3J_1UTEO0ihqfee9cVbsfzjekvRJrnzVYH0q0KuirhUcfqioJ1NedTWt1vQPWtuhxQNYtVqbxJras10zpnpnOxZZM8YnHjKFtpojPCe3sKmxKEL50m666f7AoXVMrMxGE_3pWlo_yw6EdbnWPQn1HobbsdwkXGTiM1cLXCze0UniPyfAown5MPQoSm12WvAGq9juNFgxs4Gve4k77Mn3MReZIb-ARwXfXKfqBV6VrM0iCqRHypcGzaObBm0ytXsfUuhnKdbbLHhuTkOYem9bOuObdbBhj8BQYI2tYzRVMNwg" 5 | } 6 | } -------------------------------------------------------------------------------- /acme-extensions/src/main/java/demo/acme/keycloak/audit/AcmeAuditListenerFactory.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak.audit; 2 | 3 | import com.google.auto.service.AutoService; 4 | import org.keycloak.Config; 5 | import org.keycloak.events.EventListenerProvider; 6 | import org.keycloak.events.EventListenerProviderFactory; 7 | import org.keycloak.models.KeycloakSession; 8 | import org.keycloak.models.KeycloakSessionFactory; 9 | 10 | @AutoService(EventListenerProviderFactory.class) 11 | public class AcmeAuditListenerFactory implements EventListenerProviderFactory { 12 | 13 | private static final AcmeAuditListener INSTANCE = new AcmeAuditListener(); 14 | 15 | @Override 16 | public EventListenerProvider create(KeycloakSession session) { 17 | return INSTANCE; 18 | } 19 | 20 | @Override 21 | public void init(Config.Scope config) { 22 | // NOOP 23 | } 24 | 25 | @Override 26 | public void postInit(KeycloakSessionFactory factory) { 27 | // NOOP 28 | } 29 | 30 | @Override 31 | public void close() { 32 | // NOOP 33 | } 34 | 35 | @Override 36 | public String getId() { 37 | return AcmeAuditListener.ID; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /acme-extensions/src/main/java/demo/acme/keycloak/audit/AcmeAuditListener.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak.audit; 2 | 3 | import lombok.extern.jbosslog.JBossLog; 4 | import org.keycloak.events.Event; 5 | import org.keycloak.events.EventListenerProvider; 6 | import org.keycloak.events.admin.AdminEvent; 7 | 8 | @JBossLog 9 | public class AcmeAuditListener implements EventListenerProvider { 10 | 11 | public static final String ID = "acme-audit-listener"; 12 | 13 | @Override 14 | public void onEvent(Event event) { 15 | // called for each User-Event 16 | log.infof("audit userEvent=%s type=%s realm=%suserId=%s", 17 | event, event.getType(), event.getRealmId(), event.getUserId()); 18 | } 19 | 20 | @Override 21 | public void onEvent(AdminEvent event, boolean includeRepresentation) { 22 | // called for each AdminEvent 23 | log.infof("audit adminEvent=%s type=%s resourceType=%s resourcePath=%s includeRepresentation=%s", 24 | event, event.getOperationType(), event.getResourceType(), event.getResourcePath(), includeRepresentation); 25 | } 26 | 27 | @Override 28 | public void close() { 29 | // NOOP 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /acme-extensions/src/main/java/demo/acme/keycloak/api/AcmeResource.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak.api; 2 | 3 | import org.keycloak.models.KeycloakSession; 4 | import org.keycloak.representations.AccessToken; 5 | 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Path; 8 | import javax.ws.rs.Produces; 9 | import javax.ws.rs.core.MediaType; 10 | import javax.ws.rs.core.Response; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * {@code 16 | * curl -v http://localhost:8080/auth/realms/acme/acme-resources/ping | jq -C . 17 | * } 18 | */ 19 | public class AcmeResource { 20 | 21 | private final KeycloakSession session; 22 | private final AccessToken token; 23 | 24 | public AcmeResource(KeycloakSession session, AccessToken accessToken) { 25 | this.session = session; 26 | this.token = accessToken; 27 | } 28 | 29 | @GET 30 | @Path("ping") 31 | @Produces(MediaType.APPLICATION_JSON) 32 | public Response ping() { 33 | 34 | Map payload = new HashMap<>(); 35 | payload.put("realm", session.getContext().getRealm().getName()); 36 | payload.put("user", token == null ? "anonymous" : token.getPreferredUsername()); 37 | payload.put("timestamp", System.currentTimeMillis()); 38 | 39 | return Response.ok(payload).build(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /acme-extensions/src/main/resources/META-INF/jboss-deployment-structure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.thomasdarimont.keycloak 8 | keycloak-extensions-talk 9 | pom 10 | 1.0.0-SNAPSHOT 11 | 12 | acme-extensions 13 | acme-themes 14 | acme-dist 15 | 16 | 17 | 18 | 19 | UTF-8 20 | 11 21 | ${java.version} 22 | ${java.version} 23 | thomasdarimont/acme-keycloak 24 | 25 | 26 | 12.0.4 27 | 28 | 29 | 5.7.1 30 | 3.9.1 31 | 1.6.0 32 | 33 | 34 | 1.0-rc7 35 | 1.18.16 36 | 0.35.0 37 | 2.22.2 38 | 2.22.2 39 | 40 | -------------------------------------------------------------------------------- /acme-extensions/src/main/java/demo/acme/keycloak/api/AcmeResourceProvider.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak.api; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.keycloak.authorization.util.Tokens; 5 | import org.keycloak.models.KeycloakSession; 6 | import org.keycloak.representations.AccessToken; 7 | import org.keycloak.services.resource.RealmResourceProvider; 8 | 9 | import javax.ws.rs.ForbiddenException; 10 | import javax.ws.rs.NotAuthorizedException; 11 | import javax.ws.rs.core.Response; 12 | 13 | import static javax.ws.rs.core.Response.Status.FORBIDDEN; 14 | import static javax.ws.rs.core.Response.Status.UNAUTHORIZED; 15 | 16 | @RequiredArgsConstructor 17 | public class AcmeResourceProvider implements RealmResourceProvider { 18 | 19 | public static final String ID = "acme-resources"; 20 | 21 | private final KeycloakSession session; 22 | 23 | @Override 24 | public Object getResource() { 25 | 26 | AccessToken accessToken = Tokens.getAccessToken(session); 27 | 28 | // check access 29 | // if (accessToken == null) { 30 | // throw new NotAuthorizedException("Invalid Token", Response.status(UNAUTHORIZED).build()); 31 | // } else if (!hasScope("acme.api", accessToken.getScope())) { 32 | // throw new ForbiddenException("No Access", Response.status(FORBIDDEN).build()); 33 | // } 34 | 35 | return new AcmeResource(session, accessToken); 36 | } 37 | 38 | private boolean hasScope(String requiredScope, String scope) { 39 | 40 | if (scope == null || scope.isEmpty()) { 41 | return false; 42 | } 43 | 44 | String[] scopeEntries = scope.split(" "); 45 | for (String entry : scopeEntries) { 46 | if (entry.equals(requiredScope)) { 47 | return true; 48 | } 49 | } 50 | 51 | return false; 52 | } 53 | 54 | @Override 55 | public void close() { 56 | // NOOP 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cli/onstart-0001-init.cli: -------------------------------------------------------------------------------- 1 | echo using ${env.JBOSS_HOME}/standalone/configuration/standalone.xml 2 | 3 | embed-server --server-config=standalone.xml --std-out=echo 4 | 5 | echo SETUP: Begin Keycloak custom configuration... 6 | 7 | #### EVENT LISTENERS SPI Configuration 8 | echo SETUP: Customize Keycloak Event Listener configuration 9 | # Add dedicated eventsListener config element to allow configuring elements. 10 | if (outcome == failed) of /subsystem=keycloak-server/spi=eventsListener/:read-resource 11 | echo SETUP: Add missing eventsListener SPI 12 | /subsystem=keycloak-server/spi=eventsListener:add() 13 | echo 14 | end-if 15 | 16 | echo SETUP: Configure built-in "jboss-logging" event listener 17 | if (outcome == failed) of /subsystem=keycloak-server/spi=eventsListener/provider=jboss-logging/:read-resource 18 | echo SETUP: Add missing "jboss-logging" event listener 19 | /subsystem=keycloak-server/spi=eventsListener/provider=jboss-logging:add(enabled=true) 20 | echo 21 | end-if 22 | 23 | # Propagate success events to INFO instead of DEBUG 24 | # This allows to track successful logins in log analysis 25 | /subsystem=keycloak-server/spi=eventsListener/provider=jboss-logging:write-attribute(name=properties.success-level,value=info) 26 | /subsystem=keycloak-server/spi=eventsListener/provider=jboss-logging:write-attribute(name=properties.error-level,value=warn) 27 | 28 | 29 | ### Theme Configuration ### 30 | 31 | echo SETUP: Configure theme caching 32 | /subsystem=keycloak-server/theme=defaults:write-attribute(name=cacheThemes,value=${env.KEYCLOAK_THEME_CACHING:true}) 33 | /subsystem=keycloak-server/theme=defaults:write-attribute(name=cacheTemplates,value=${env.KEYCLOAK_THEME_TEMPLATE_CACHING:true}) 34 | /subsystem=keycloak-server/theme=defaults:write-attribute(name=welcomeTheme,value=${env.KEYCLOAK_WELCOME_THEME:keycloak}) 35 | /subsystem=keycloak-server/theme=defaults:write-attribute(name=default,value=${env.KEYCLOAK_DEFAULT_THEME:keycloak}) 36 | 37 | 38 | echo SETUP: Finished Keycloak custom configuration. 39 | 40 | stop-embedded-server -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Easy Keycloak Extension Development 2 | --- 3 | 4 | This repository contains the code & slides of my talk `Keycloak Extension Development - Overview & Best Practices`. 5 | 6 | This example shows how to develop and deploy a set of Keycloak extensions, custom themens and configuration to a Keycloak docker container. 7 | In addition to that, the example also shows how to write integration tests via [Keycloak-Testcontainers](https://github.com/dasniko/testcontainers-keycloak) and how to package all extensions and themes as a 8 | custom docker image. 9 | 10 | The example contains the following Keycloak extensions: 11 | - OIDC ProtocolMapper to compute age-class information based on a `birthdate` user attribute: `AgeInfoMapper` 12 | - Audit Event Listener sketch to forward certain Keycloak user and admin events to an external service: `AcmeAuditListener` 13 | - Custom REST Endpoint the can expose additional custom APIs: `AcmeResource` 14 | 15 | # Some Highlights 16 | - Support for deploying extensions to running Keycloak container 17 | - Support for instant reloading of theme and extension code changes 18 | - Support Keycloak configuration customization via CLI scripts 19 | - Examples for Integration Tests with Keycloak-Testcontainers 20 | 21 | # Build 22 | The example can be build with the following maven command: 23 | ``` 24 | mvn clean verify 25 | ``` 26 | 27 | ## Build with Integration Tests 28 | The example can be build with integration tests by running the following maven command: 29 | ``` 30 | mvn clean verify -Pwith-integration-tests 31 | ``` 32 | 33 | ## Build Docker Image 34 | To build a custom Keycloak Docker image that contains the custom extensions and themes, you can run the following command: 35 | ``` 36 | mvn clean verify -Pwith-integration-tests io.fabric8:docker-maven-plugin:build 37 | ``` 38 | 39 | # Run 40 | 41 | ## Start Keycloak container with docker-compose 42 | 43 | Keycloak will be available on http://localhost:8080/auth. 44 | The default Keycloak admin username is `admin` with password `admin`. 45 | 46 | You can start the Keycloak container via: 47 | ``` 48 | docker-compose up 49 | ``` 50 | 51 | ## Run custom Docker Image 52 | The custom docker image created during the build can be stared with the following command: 53 | ``` 54 | docker run \ 55 | --name acme-keycloak \ 56 | -e KEYCLOAK_USER=admin \ 57 | -e KEYCLOAK_PASSWORD=admin \ 58 | -e KEYCLOAK_IMPORT=/opt/jboss/imex/acme-realm.json \ 59 | -v $PWD/imex:/opt/jboss/imex:z \ 60 | -it \ 61 | --rm \ 62 | -p 8080:8080 \ 63 | thomasdarimont/acme-keycloak:latest 64 | ``` 65 | 66 | # Example environment 67 | 68 | The example environment contains a Keycloak realm named `acme`, which contains a simple demo application as well as a test user. 69 | The test user has the username `tester` and password `test`. 70 | 71 | ### Example App 72 | 73 | A simple demo app can be used to show information from the Access-Token, ID-Token and UserInfo endpoint provided by Keycloak. 74 | 75 | The demo app can be started by running `etc/runDemoApp.sh` and will be accessible via http://localhost:4000. 76 | 77 | # Scripts 78 | 79 | ## Manually trigger Extension Deployment 80 | ``` 81 | etc/triggerDockerExtensionDeploy.sh 82 | ``` 83 | 84 | ## Exporting the 'Acme' Realm 85 | ``` 86 | etc/exportRealm.sh 87 | ``` 88 | -------------------------------------------------------------------------------- /acme-dist/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | keycloak-extensions-talk 7 | com.github.thomasdarimont.keycloak 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | acme-dist 13 | 14 | 15 | 16 | io.fabric8 17 | docker-maven-plugin 18 | ${docker-maven-plugin.version} 19 | 20 | 21 | 22 | docker-build-100 23 | docker 24 | 25 | build 26 | 27 | 28 | 29 | 30 | 31 | true 32 | true 33 | 34 | 35 | 36 | ${docker-image} 37 | 38 | 39 | ${project.version} 40 | 41 | 42 | 43 | keycloak 44 | 45 | 46 | 47 | 48 | 49 | ../acme-extensions/target 50 | 51 | acme-extensions-${project.version}.jar 52 | 53 | acme-extensions 54 | 55 | 56 | 57 | ../acme-themes/target/classes/theme/acme 58 | acme-theme 59 | 60 | 61 | 62 | ../cli 63 | cli 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /acme-extensions/src/main/java/demo/acme/keycloak/oidc/AgeInfoMapper.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak.oidc; 2 | 3 | import com.google.auto.service.AutoService; 4 | import org.keycloak.models.ClientSessionContext; 5 | import org.keycloak.models.KeycloakSession; 6 | import org.keycloak.models.ProtocolMapperModel; 7 | import org.keycloak.models.UserModel; 8 | import org.keycloak.models.UserSessionModel; 9 | import org.keycloak.protocol.ProtocolMapper; 10 | import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper; 11 | import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper; 12 | import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; 13 | import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper; 14 | import org.keycloak.protocol.oidc.mappers.UserInfoTokenMapper; 15 | import org.keycloak.provider.ProviderConfigProperty; 16 | import org.keycloak.representations.IDToken; 17 | 18 | import java.time.LocalDate; 19 | import java.time.LocalDateTime; 20 | import java.time.format.DateTimeParseException; 21 | import java.time.temporal.ChronoUnit; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Optional; 25 | 26 | @AutoService(ProtocolMapper.class) 27 | public class AgeInfoMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper { 28 | 29 | static final String PROVIDER_ID = "acme-ageinfo-mapper"; 30 | 31 | static final List CONFIG_PROPERTIES; 32 | 33 | public static final String AGE_CLASS_CLAIM = "acme_age_class"; 34 | 35 | static { 36 | 37 | List configProperties = new ArrayList<>(); 38 | OIDCAttributeMapperHelper.addIncludeInTokensConfig(configProperties, AgeInfoMapper.class); 39 | 40 | CONFIG_PROPERTIES = configProperties; 41 | } 42 | 43 | @Override 44 | public String getId() { 45 | return PROVIDER_ID; 46 | } 47 | 48 | @Override 49 | public String getDisplayType() { 50 | return "Acme: AgeInfo"; 51 | } 52 | 53 | @Override 54 | public String getHelpText() { 55 | return "Exposes the user's age-class as claim"; 56 | } 57 | 58 | @Override 59 | public String getDisplayCategory() { 60 | return TOKEN_MAPPER_CATEGORY; 61 | } 62 | 63 | @Override 64 | public List getConfigProperties() { 65 | return CONFIG_PROPERTIES; 66 | } 67 | 68 | @Override 69 | protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, UserSessionModel userSession, KeycloakSession keycloakSession, ClientSessionContext clientSessionCtx) { 70 | 71 | UserModel user = userSession.getUser(); 72 | String ageClass = computeAgeClass(user); 73 | 74 | token.getOtherClaims().put(AGE_CLASS_CLAIM, ageClass); 75 | } 76 | 77 | private String computeAgeClass(UserModel user) { 78 | 79 | return Optional.ofNullable(user.getFirstAttribute("birthdate")).map(birthdateString -> { 80 | 81 | LocalDate birthdate; 82 | try { 83 | birthdate = LocalDate.parse(birthdateString); 84 | } catch (DateTimeParseException ex) { 85 | return "invalid"; 86 | } 87 | 88 | long ageInYears = ChronoUnit.YEARS.between(birthdate, LocalDateTime.now()); 89 | String ageClass = "under16"; 90 | 91 | if (ageInYears >= 16) { 92 | ageClass = "over16"; 93 | } 94 | if (ageInYears >= 18) { 95 | ageClass = "over18"; 96 | } 97 | if (ageInYears >= 21) { 98 | ageClass = "over21"; 99 | } 100 | 101 | return ageClass; 102 | }).orElse("missing"); 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /acme-extensions/src/test/java/demo/acme/keycloak/KeycloakTestSupport.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak; 2 | 3 | import dasniko.testcontainers.keycloak.KeycloakContainer; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import org.jboss.resteasy.client.jaxrs.ResteasyClient; 7 | import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; 8 | import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; 9 | import org.keycloak.admin.client.CreatedResponseUtil; 10 | import org.keycloak.admin.client.resource.RealmResource; 11 | import org.keycloak.admin.client.token.TokenService; 12 | import org.keycloak.representations.idm.CredentialRepresentation; 13 | import org.keycloak.representations.idm.UserRepresentation; 14 | import org.testcontainers.containers.output.OutputFrame; 15 | import org.testcontainers.utility.MountableFile; 16 | 17 | import javax.ws.rs.core.Response; 18 | import javax.ws.rs.core.UriBuilder; 19 | import java.nio.file.Path; 20 | import java.util.List; 21 | import java.util.function.Consumer; 22 | 23 | public class KeycloakTestSupport { 24 | 25 | public static final String MASTER_REALM = "master"; 26 | 27 | public static final String ADMIN_CLI = "admin-cli"; 28 | 29 | public static KeycloakContainer createKeycloakContainer(boolean keycloakLocal, String realmImportFileName) { 30 | 31 | if (keycloakLocal) { 32 | return new LocalKeycloak("http://localhost:8080/auth", "admin", "admin"); 33 | } 34 | 35 | return new KeycloakContainer("quay.io/keycloak/keycloak:12.0.4") 36 | // .withRealmImportFile(REALM_IMPORT_FILE) // broken for integration-tests outside of the IDE 37 | .withCopyFileToContainer(MountableFile.forHostPath(Path.of("target/classes/" + realmImportFileName)), "/tmp/" + realmImportFileName) 38 | .withEnv("KEYCLOAK_IMPORT", "/tmp/" + realmImportFileName) 39 | .withCopyFileToContainer(MountableFile.forHostPath(Path.of("../cli/onstart-0001-init.cli")), "/opt/jboss/startup-scripts/onstart-0001-init.cli") 40 | .withExtensionClassesFrom("target/classes"); 41 | } 42 | 43 | public static TokenService getTokenService(KeycloakContainer keycloak) { 44 | return getResteasyWebTarget(keycloak).proxy(TokenService.class); 45 | } 46 | 47 | public static ResteasyWebTarget getResteasyWebTarget(KeycloakContainer keycloak) { 48 | ResteasyClient client = new ResteasyClientBuilder().build(); 49 | return client.target(UriBuilder.fromPath(keycloak.getAuthServerUrl())); 50 | } 51 | 52 | public static UserRef createOrUpdateTestUser(RealmResource realm, String username, String password, Consumer adjuster) { 53 | 54 | List existingUsers = realm.users().search(username, true); 55 | 56 | String userId; 57 | UserRepresentation userRep; 58 | 59 | if (existingUsers.isEmpty()) { 60 | userRep = new UserRepresentation(); 61 | userRep.setUsername(username); 62 | userRep.setEnabled(true); 63 | adjuster.accept(userRep); 64 | try (Response response = realm.users().create(userRep)) { 65 | userId = CreatedResponseUtil.getCreatedId(response); 66 | } catch (Exception ex) { 67 | throw new RuntimeException(ex); 68 | } 69 | } else { 70 | userRep = existingUsers.get(0); 71 | adjuster.accept(userRep); 72 | userId = userRep.getId(); 73 | } 74 | 75 | CredentialRepresentation passwordRep = new CredentialRepresentation(); 76 | passwordRep.setType(CredentialRepresentation.PASSWORD); 77 | passwordRep.setValue(password); 78 | realm.users().get(userId).resetPassword(passwordRep); 79 | 80 | return new UserRef(userId, username); 81 | } 82 | 83 | @Data 84 | @AllArgsConstructor 85 | public static class UserRef { 86 | String userId; 87 | String username; 88 | } 89 | 90 | 91 | @Data 92 | @AllArgsConstructor 93 | public static class LocalKeycloak extends KeycloakContainer { 94 | 95 | String authServerUrl; 96 | String adminUsername; 97 | String adminPassword; 98 | 99 | public void start() { 100 | // NOOP 101 | } 102 | 103 | @Override 104 | public void followOutput(Consumer consumer) { 105 | // NOOP 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /acme-extensions/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | keycloak-extensions-talk 7 | com.github.thomasdarimont.keycloak 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | acme-extensions 13 | 14 | 15 | 16 | 17 | org.keycloak 18 | keycloak-server-spi 19 | ${keycloak.version} 20 | provided 21 | 22 | 23 | 24 | 25 | org.keycloak 26 | keycloak-server-spi-private 27 | ${keycloak.version} 28 | provided 29 | 30 | 31 | 32 | org.keycloak 33 | keycloak-services 34 | ${keycloak.version} 35 | provided 36 | 37 | 38 | 39 | org.keycloak 40 | keycloak-admin-client 41 | ${keycloak.version} 42 | test 43 | 44 | 45 | 46 | org.junit.jupiter 47 | junit-jupiter 48 | ${junit-jupiter.version} 49 | test 50 | 51 | 52 | 53 | org.assertj 54 | assertj-core 55 | ${assertj-core.version} 56 | test 57 | 58 | 59 | 60 | com.github.dasniko 61 | testcontainers-keycloak 62 | ${testcontainers-keycloak.version} 63 | test 64 | 65 | 66 | 67 | org.projectlombok 68 | lombok 69 | ${lombok.version} 70 | provided 71 | true 72 | 73 | 74 | 75 | com.google.auto.service 76 | auto-service 77 | ${auto-service.version} 78 | provided 79 | true 80 | 81 | 82 | 83 | 84 | with-integration-tests 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-failsafe-plugin 90 | ${maven-failsafe-plugin.version} 91 | 92 | 93 | integration-test 94 | 95 | integration-test 96 | verify 97 | 98 | 99 | 100 | 101 | 102 | **/*IntegrationTest.java 103 | 104 | once 105 | 106 | true 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | org.apache.maven.plugins 119 | maven-surefire-plugin 120 | ${maven-surefire-plugin.version} 121 | 122 | 123 | **/*IntegrationTest.java 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | Notes 2 | --- 3 | 4 | ``` 5 | docker run \ 6 | --name keycloak-extdev \ 7 | --rm \ 8 | -e KEYCLOAK_USER=admin \ 9 | -e KEYCLOAK_PASSWORD=admin \ 10 | -e KEYCLOAK_THEME_CACHING=false \ 11 | -e KEYCLOAK_THEME_TEMPLATE_CACHING=false \ 12 | -v $PWD/acme-extensions/target/classes:/opt/jboss/keycloak/standalone/deployments/acme-extensions.jar:z \ 13 | -v $PWD/acme-themes/target/classes/theme/acme:/opt/jboss/keycloak/themes/acme:z \ 14 | -v $PWD/testrun/data:/opt/jboss/keycloak/standalone/data:z \ 15 | -v $PWD/cli:/opt/jboss/startup-scripts:z \ 16 | -v $PWD/imex:/opt/jboss/imex:z \ 17 | -p 8080:8080 \ 18 | -p 8787:8787 \ 19 | quay.io/keycloak/keycloak:12.0.4 --debug '*:8787' --server-config standalone.xml 20 | ``` 21 | 22 | ``` 23 | mvn clean verify io.fabric8:docker-maven-plugin:build -Pwith-integration-tests 24 | 25 | mvn clean verify -Pwith-integration-tests 26 | ``` 27 | 28 | 29 | # Scratch Notes 30 | 31 | ## Prepare Scratch Keycloak 32 | 33 | ``` 34 | cd scratch 35 | cp ~/Downloads/keycloak-12.0.4.tar.gz . 36 | tar xzf keycloak-12.0.4.tar.gz 37 | keycloak-12.0.4/bin/add-user-keycloak.sh --user admin --password admin 38 | ``` 39 | 40 | ## Start Scratch Keycloak 41 | ``` 42 | cd scratch/keycloak-12.0.4 43 | bin/standalone.sh --debug -Dwildfly.statistics-enabled=true -c standalone.xml 44 | ``` 45 | 46 | ## Deploy extensions 47 | ``` 48 | cp acme-extensions/target/acme-extensions-1.0.0-SNAPSHOT.jar scratch/keycloak-12.0.4/standalone/deployments 49 | ``` 50 | 51 | ## Undeploy extensions 52 | ``` 53 | rm scratch/keycloak-12.0.4/standalone/deployments/*.jar 54 | ``` 55 | 56 | 57 | Scratch 58 | --- 59 | 60 | `AcmeAuditListener.java` 61 | ```java 62 | package demo.acme.keycloak.audit; 63 | 64 | import org.jboss.logging.Logger; 65 | import org.keycloak.events.Event; 66 | import org.keycloak.events.EventListenerProvider; 67 | import org.keycloak.events.admin.AdminEvent; 68 | 69 | public class AcmeAuditListener implements EventListenerProvider { 70 | 71 | public static final String ID = "acme-audit-listener"; 72 | 73 | private static final Logger LOG = Logger.getLogger(AcmeAuditListener.class); 74 | 75 | @Override 76 | public void onEvent(Event event) { 77 | // called for each User-Event 78 | LOG.infof("audit userEvent=%s type=%s realm=%suserId=%s", 79 | event, event.getType(), event.getRealmId(), event.getUserId()); 80 | } 81 | 82 | @Override 83 | public void onEvent(AdminEvent event, boolean includeRepresentation) { 84 | // called for each AdminEvent 85 | LOG.infof("audit adminEvent=%s type=%s resourceType=%s resourcePath=%s includeRepresentation=%s", 86 | event, event.getOperationType(), event.getResourceType(), event.getResourcePath(), includeRepresentation); 87 | } 88 | 89 | @Override 90 | public void close() { 91 | // NOOP 92 | } 93 | } 94 | ``` 95 | 96 | `AcmeAuditListenerFactory.java` 97 | ```java 98 | package demo.acme.keycloak.audit; 99 | 100 | import org.keycloak.Config; 101 | import org.keycloak.events.EventListenerProvider; 102 | import org.keycloak.events.EventListenerProviderFactory; 103 | import org.keycloak.models.KeycloakSession; 104 | import org.keycloak.models.KeycloakSessionFactory; 105 | 106 | // src/main/resources/META-INF/servcies -> org.keycloak.events.EventListenerProviderFactory 107 | public class AcmeAuditListenerFactory implements EventListenerProviderFactory { 108 | 109 | private static final AcmeAuditListener INSTANCE = new AcmeAuditListener(); 110 | 111 | @Override 112 | public String getId() { 113 | return AcmeAuditListener.ID; 114 | } 115 | 116 | @Override 117 | public EventListenerProvider create(KeycloakSession session) { 118 | // return singleton instance, or create new AcmeAuditListener(session) if you need KeycloakSession access. 119 | return INSTANCE; 120 | } 121 | 122 | @Override 123 | public void init(Config.Scope config) { 124 | // we could read settings from the provider config in standalone(-ha).xml 125 | } 126 | 127 | @Override 128 | public void postInit(KeycloakSessionFactory factory) { 129 | // we could init our provider with information from other providers 130 | } 131 | 132 | @Override 133 | public void close() { 134 | // close resources if providers 135 | } 136 | } 137 | ``` 138 | 139 | 140 | `org.keycloak.events.EventListenerProviderFactory` 141 | ``` 142 | demo.acme.keycloak.audit.AcmeAuditListenerFactory 143 | ``` 144 | 145 | Test for Audit Listener in `AcmeKeycloakIntegrationTest.java` 146 | ```java 147 | @Test 148 | public void auditListenerShouldPrintLogMessage() throws Exception{ 149 | 150 | Assumptions.assumeTrue(!keycloakLocal); 151 | 152 | ToStringConsumer consumer = new ToStringConsumer(); 153 | keycloak.followOutput(consumer); 154 | 155 | TokenService tokenService = KeycloakTestSupport.getTokenService(keycloak); 156 | 157 | // trigger user login via ROPC 158 | AccessTokenResponse accessTokenResponse = tokenService.grantToken(ACME_REALM, new Form() 159 | .param("grant_type", "password") 160 | .param("username", "tester") 161 | .param("password", TEST_USER_PASSWORD) 162 | .param("client_id", TEST_CLIENT) 163 | .param("scope", "openid acme.profile acme.ageinfo") 164 | .asMap()); 165 | 166 | // Allow the container log to flush 167 | TimeUnit.MILLISECONDS.sleep(750); 168 | 169 | assertThat(consumer.toUtf8String()).contains("audit userEvent"); 170 | } 171 | 172 | ``` 173 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !testrun/.gitkeep 2 | testrun/ 3 | !scratch/.gitkeep 4 | scratch/ 5 | *.jfr 6 | 7 | # Created by https://www.gitignore.io/api/osx,java,maven,gradle,eclipse,intellij+all,visualstudiocode 8 | # Edit at https://www.gitignore.io/?templates=osx,java,maven,gradle,eclipse,intellij+all,visualstudiocode 9 | 10 | ### Eclipse ### 11 | 12 | .metadata 13 | bin/ 14 | tmp/ 15 | *.tmp 16 | *.bak 17 | *.swp 18 | *~.nib 19 | local.properties 20 | .settings/ 21 | .loadpath 22 | .recommenders 23 | 24 | # External tool builders 25 | .externalToolBuilders/ 26 | 27 | # Locally stored "Eclipse launch configurations" 28 | *.launch 29 | 30 | # PyDev specific (Python IDE for Eclipse) 31 | *.pydevproject 32 | 33 | # CDT-specific (C/C++ Development Tooling) 34 | .cproject 35 | 36 | # CDT- autotools 37 | .autotools 38 | 39 | # Java annotation processor (APT) 40 | .factorypath 41 | 42 | # PDT-specific (PHP Development Tools) 43 | .buildpath 44 | 45 | # sbteclipse plugin 46 | .target 47 | 48 | # Tern plugin 49 | .tern-project 50 | 51 | # TeXlipse plugin 52 | .texlipse 53 | 54 | # STS (Spring Tool Suite) 55 | .springBeans 56 | 57 | # Code Recommenders 58 | .recommenders/ 59 | 60 | # Annotation Processing 61 | .apt_generated/ 62 | 63 | # Scala IDE specific (Scala & Java development for Eclipse) 64 | .cache-main 65 | .scala_dependencies 66 | .worksheet 67 | 68 | ### Eclipse Patch ### 69 | # Eclipse Core 70 | .project 71 | 72 | # JDT-specific (Eclipse Java Development Tools) 73 | .classpath 74 | 75 | # Annotation Processing 76 | .apt_generated 77 | 78 | .sts4-cache/ 79 | 80 | ### Intellij+all ### 81 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 82 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 83 | 84 | # User-specific stuff 85 | .idea/**/workspace.xml 86 | .idea/**/tasks.xml 87 | .idea/**/usage.statistics.xml 88 | .idea/**/dictionaries 89 | .idea/**/shelf 90 | 91 | # Generated files 92 | .idea/**/contentModel.xml 93 | 94 | # Sensitive or high-churn files 95 | .idea/**/dataSources/ 96 | .idea/**/dataSources.ids 97 | .idea/**/dataSources.local.xml 98 | .idea/**/sqlDataSources.xml 99 | .idea/**/dynamic.xml 100 | .idea/**/uiDesigner.xml 101 | .idea/**/dbnavigator.xml 102 | 103 | # Gradle 104 | .idea/**/gradle.xml 105 | .idea/**/libraries 106 | 107 | # Gradle and Maven with auto-import 108 | # When using Gradle or Maven with auto-import, you should exclude module files, 109 | # since they will be recreated, and may cause churn. Uncomment if using 110 | # auto-import. 111 | # .idea/modules.xml 112 | # .idea/*.iml 113 | # .idea/modules 114 | 115 | # CMake 116 | cmake-build-*/ 117 | 118 | # Mongo Explorer plugin 119 | .idea/**/mongoSettings.xml 120 | 121 | # File-based project format 122 | *.iws 123 | 124 | # IntelliJ 125 | out/ 126 | 127 | # mpeltonen/sbt-idea plugin 128 | .idea_modules/ 129 | 130 | # JIRA plugin 131 | atlassian-ide-plugin.xml 132 | 133 | # Cursive Clojure plugin 134 | .idea/replstate.xml 135 | 136 | # Crashlytics plugin (for Android Studio and IntelliJ) 137 | com_crashlytics_export_strings.xml 138 | crashlytics.properties 139 | crashlytics-build.properties 140 | fabric.properties 141 | 142 | # Editor-based Rest Client 143 | .idea/httpRequests 144 | 145 | # Android studio 3.1+ serialized cache file 146 | .idea/caches/build_file_checksums.ser 147 | 148 | ### Intellij+all Patch ### 149 | # Ignores the whole .idea folder and all .iml files 150 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 151 | 152 | .idea/ 153 | 154 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 155 | 156 | *.iml 157 | modules.xml 158 | .idea/misc.xml 159 | *.ipr 160 | 161 | # Sonarlint plugin 162 | .idea/sonarlint 163 | 164 | ### Java ### 165 | # Compiled class file 166 | *.class 167 | 168 | # Log file 169 | *.log 170 | 171 | # BlueJ files 172 | *.ctxt 173 | 174 | # Mobile Tools for Java (J2ME) 175 | .mtj.tmp/ 176 | 177 | # Package Files # 178 | *.jar 179 | *.war 180 | *.nar 181 | *.ear 182 | *.zip 183 | *.tar.gz 184 | *.rar 185 | 186 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 187 | hs_err_pid* 188 | 189 | ### Maven ### 190 | target/ 191 | pom.xml.tag 192 | pom.xml.releaseBackup 193 | pom.xml.versionsBackup 194 | pom.xml.next 195 | release.properties 196 | dependency-reduced-pom.xml 197 | buildNumber.properties 198 | .mvn/timing.properties 199 | .mvn/wrapper/maven-wrapper.jar 200 | 201 | ### OSX ### 202 | # General 203 | .DS_Store 204 | .AppleDouble 205 | .LSOverride 206 | 207 | # Icon must end with two \r 208 | Icon 209 | 210 | # Thumbnails 211 | ._* 212 | 213 | # Files that might appear in the root of a volume 214 | .DocumentRevisions-V100 215 | .fseventsd 216 | .Spotlight-V100 217 | .TemporaryItems 218 | .Trashes 219 | .VolumeIcon.icns 220 | .com.apple.timemachine.donotpresent 221 | 222 | # Directories potentially created on remote AFP share 223 | .AppleDB 224 | .AppleDesktop 225 | Network Trash Folder 226 | Temporary Items 227 | .apdisk 228 | 229 | ### VisualStudioCode ### 230 | .vscode/* 231 | !.vscode/settings.json 232 | !.vscode/tasks.json 233 | !.vscode/launch.json 234 | !.vscode/extensions.json 235 | 236 | ### VisualStudioCode Patch ### 237 | # Ignore all local history of files 238 | .history 239 | 240 | ### Gradle ### 241 | .gradle 242 | build/ 243 | 244 | # Ignore Gradle GUI config 245 | gradle-app.setting 246 | 247 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 248 | !gradle-wrapper.jar 249 | 250 | # Cache of project 251 | .gradletasknamecache 252 | 253 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 254 | # gradle/wrapper/gradle-wrapper.properties 255 | 256 | ### Gradle Patch ### 257 | **/build/ 258 | 259 | # End of https://www.gitignore.io/api/osx,java,maven,gradle,eclipse,intellij+all,visualstudiocode 260 | -------------------------------------------------------------------------------- /acme-extensions/src/test/java/demo/acme/keycloak/AcmeKeycloakIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package demo.acme.keycloak; 2 | 3 | import dasniko.testcontainers.keycloak.KeycloakContainer; 4 | import demo.acme.keycloak.KeycloakTestSupport.UserRef; 5 | import demo.acme.keycloak.oidc.AgeInfoMapper; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.jupiter.api.AfterAll; 8 | import org.junit.jupiter.api.Assumptions; 9 | import org.junit.jupiter.api.BeforeAll; 10 | import org.junit.jupiter.api.Test; 11 | import org.keycloak.TokenVerifier; 12 | import org.keycloak.admin.client.Keycloak; 13 | import org.keycloak.admin.client.resource.RealmResource; 14 | import org.keycloak.admin.client.token.TokenService; 15 | import org.keycloak.representations.AccessTokenResponse; 16 | import org.keycloak.representations.IDToken; 17 | import org.testcontainers.containers.output.Slf4jLogConsumer; 18 | import org.testcontainers.containers.output.ToStringConsumer; 19 | import org.testcontainers.shaded.com.google.common.collect.ImmutableMap; 20 | 21 | import javax.ws.rs.Consumes; 22 | import javax.ws.rs.GET; 23 | import javax.ws.rs.HeaderParam; 24 | import javax.ws.rs.PathParam; 25 | import javax.ws.rs.core.Form; 26 | import javax.ws.rs.core.MediaType; 27 | import java.nio.file.Files; 28 | import java.nio.file.Path; 29 | import java.time.LocalDate; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.concurrent.TimeUnit; 33 | 34 | import static demo.acme.keycloak.KeycloakTestSupport.ADMIN_CLI; 35 | import static demo.acme.keycloak.KeycloakTestSupport.MASTER_REALM; 36 | import static org.assertj.core.api.Assertions.assertThat; 37 | 38 | @Slf4j 39 | public class AcmeKeycloakIntegrationTest { 40 | 41 | public static final String ACME_REALM = "acme"; 42 | 43 | public static final String TEST_CLIENT = "test-client"; 44 | 45 | public static final String TEST_USER_PASSWORD = "test"; 46 | 47 | public static final String REALM_IMPORT_FILE = "acme-realm.json"; 48 | 49 | public static KeycloakContainer keycloak; 50 | 51 | static boolean keycloakLocal = false; 52 | 53 | @BeforeAll 54 | public static void beforeAll() throws Exception { 55 | 56 | // use the previously copied realm file 57 | if (!Path.of("target/classes/" + REALM_IMPORT_FILE).toFile().exists()) { 58 | Files.copy(Path.of("../imex/" + REALM_IMPORT_FILE), Path.of("target/classes/" + REALM_IMPORT_FILE)); 59 | } 60 | 61 | if (!Path.of("target/classes/cli/onstart-0001-init.cli").toFile().exists()) { 62 | Path targetFile = Path.of("target/classes/cli/onstart-0001-init.cli"); 63 | targetFile.getParent().toFile().mkdirs(); 64 | Files.copy(Path.of("../cli/onstart-0001-init.cli"), targetFile); 65 | } 66 | 67 | keycloak = KeycloakTestSupport.createKeycloakContainer(keycloakLocal, REALM_IMPORT_FILE); 68 | keycloak.withReuse(true); 69 | keycloak.start(); 70 | keycloak.followOutput(new Slf4jLogConsumer(log)); 71 | } 72 | 73 | @AfterAll 74 | public static void afterAll() { 75 | if (keycloak != null) { 76 | keycloak.stop(); 77 | } 78 | } 79 | 80 | @Test 81 | public void auditListenerShouldPrintLogMessage() throws Exception { 82 | 83 | Assumptions.assumeTrue(!keycloakLocal); 84 | 85 | ToStringConsumer consumer = new ToStringConsumer(); 86 | keycloak.followOutput(consumer); 87 | 88 | TokenService tokenService = KeycloakTestSupport.getTokenService(keycloak); 89 | 90 | // trigger user login via ROPC 91 | AccessTokenResponse accessTokenResponse = tokenService.grantToken(ACME_REALM, new Form() 92 | .param("grant_type", "password") 93 | .param("username", "tester") 94 | .param("password", TEST_USER_PASSWORD) 95 | .param("client_id", TEST_CLIENT) 96 | .param("scope", "openid acme.profile acme.ageinfo") 97 | .asMap()); 98 | 99 | // Allow the container log to flush 100 | TimeUnit.MILLISECONDS.sleep(750); 101 | 102 | assertThat(consumer.toUtf8String()).contains("audit userEvent"); 103 | } 104 | 105 | @Test 106 | public void ageInfoMapperShouldAddAgeClassClaim() throws Exception { 107 | 108 | Keycloak keycloakAdminClient = Keycloak.getInstance(keycloak.getAuthServerUrl(), MASTER_REALM, 109 | keycloak.getAdminUsername(), keycloak.getAdminPassword(), ADMIN_CLI); 110 | 111 | RealmResource acmeRealm = keycloakAdminClient.realm(ACME_REALM); 112 | 113 | UserRef user22Years = KeycloakTestSupport.createOrUpdateTestUser(acmeRealm, "test-user-age22", TEST_USER_PASSWORD, user -> { 114 | user.setFirstName("Firstname"); 115 | user.setLastName("Lastname"); 116 | user.setAttributes(ImmutableMap.of("birthdate", List.of(LocalDate.now().minusYears(22).toString()))); 117 | }); 118 | 119 | TokenService tokenService = KeycloakTestSupport.getTokenService(keycloak); 120 | 121 | AccessTokenResponse accessTokenResponse = tokenService.grantToken(ACME_REALM, new Form() 122 | .param("grant_type", "password") 123 | .param("username", user22Years.getUsername()) 124 | .param("password", TEST_USER_PASSWORD) 125 | .param("client_id", TEST_CLIENT) 126 | .param("scope", "openid acme.profile acme.ageinfo") 127 | .asMap()); 128 | 129 | // System.out.println("Token: " + accessTokenResponse.getToken()); 130 | 131 | // parse the received id-token 132 | TokenVerifier verifier = TokenVerifier.create(accessTokenResponse.getIdToken(), IDToken.class); 133 | verifier.parse(); 134 | 135 | // check for the custom claim 136 | IDToken accessToken = verifier.getToken(); 137 | String ageInfoClaim = (String) accessToken.getOtherClaims().get(AgeInfoMapper.AGE_CLASS_CLAIM); 138 | 139 | assertThat(ageInfoClaim).isNotNull(); 140 | assertThat(ageInfoClaim).isEqualTo("over21"); 141 | } 142 | 143 | @Test 144 | public void pingResourceShouldBeAccessibleForUser() { 145 | 146 | TokenService tokenService = KeycloakTestSupport.getTokenService(keycloak); 147 | 148 | AccessTokenResponse accessTokenResponse = tokenService.grantToken(ACME_REALM, new Form() 149 | .param("grant_type", "password") 150 | .param("username", "tester") 151 | .param("password", TEST_USER_PASSWORD) 152 | .param("client_id", TEST_CLIENT) 153 | .param("scope", "openid acme.profile acme.api") 154 | .asMap()); 155 | 156 | String accessToken = accessTokenResponse.getToken(); 157 | System.out.println("Token: " + accessToken); 158 | 159 | AcmeResources acmeResources = KeycloakTestSupport.getResteasyWebTarget(keycloak).proxy(AcmeResources.class); 160 | Map response = acmeResources.ping(ACME_REALM, "Bearer " + accessToken); 161 | System.out.println(response); 162 | 163 | assertThat(response).isNotNull(); 164 | assertThat(response.get("user")).isEqualTo("tester"); 165 | } 166 | 167 | 168 | interface AcmeResources { 169 | 170 | @GET 171 | @Consumes(MediaType.APPLICATION_JSON) 172 | @javax.ws.rs.Path("/realms/{realm}/acme-resources/ping") 173 | Map ping(@PathParam("realm") String realm, @HeaderParam("Authorization") String token); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /demoapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mini SPA Demo 8 | 9 | 107 | 108 | 109 | 110 | 111 |
112 |

ClientId:

113 |
114 | 115 | 124 | 125 | 156 | 157 | 173 | 174 | 190 | 191 | 207 | 208 | 209 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /imex/acme-realm.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "acme", 3 | "realm" : "acme", 4 | "notBefore" : 0, 5 | "revokeRefreshToken" : false, 6 | "refreshTokenMaxReuse" : 0, 7 | "accessTokenLifespan" : 300, 8 | "accessTokenLifespanForImplicitFlow" : 900, 9 | "ssoSessionIdleTimeout" : 1800, 10 | "ssoSessionMaxLifespan" : 36000, 11 | "ssoSessionIdleTimeoutRememberMe" : 0, 12 | "ssoSessionMaxLifespanRememberMe" : 0, 13 | "offlineSessionIdleTimeout" : 2592000, 14 | "offlineSessionMaxLifespanEnabled" : false, 15 | "offlineSessionMaxLifespan" : 5184000, 16 | "clientSessionIdleTimeout" : 0, 17 | "clientSessionMaxLifespan" : 0, 18 | "clientOfflineSessionIdleTimeout" : 0, 19 | "clientOfflineSessionMaxLifespan" : 0, 20 | "accessCodeLifespan" : 60, 21 | "accessCodeLifespanUserAction" : 300, 22 | "accessCodeLifespanLogin" : 1800, 23 | "actionTokenGeneratedByAdminLifespan" : 43200, 24 | "actionTokenGeneratedByUserLifespan" : 300, 25 | "enabled" : true, 26 | "sslRequired" : "external", 27 | "registrationAllowed" : false, 28 | "registrationEmailAsUsername" : false, 29 | "rememberMe" : false, 30 | "verifyEmail" : false, 31 | "loginWithEmailAllowed" : true, 32 | "duplicateEmailsAllowed" : false, 33 | "resetPasswordAllowed" : false, 34 | "editUsernameAllowed" : false, 35 | "bruteForceProtected" : false, 36 | "permanentLockout" : false, 37 | "maxFailureWaitSeconds" : 900, 38 | "minimumQuickLoginWaitSeconds" : 60, 39 | "waitIncrementSeconds" : 60, 40 | "quickLoginCheckMilliSeconds" : 1000, 41 | "maxDeltaTimeSeconds" : 43200, 42 | "failureFactor" : 30, 43 | "roles" : { 44 | "realm" : [ { 45 | "id" : "bd6a1bbb-ebe6-405d-806c-e64b9fcca028", 46 | "name" : "offline_access", 47 | "description" : "${role_offline-access}", 48 | "composite" : false, 49 | "clientRole" : false, 50 | "containerId" : "acme", 51 | "attributes" : { } 52 | }, { 53 | "id" : "5b815c11-9dcc-46f1-91c8-7a5e53f034dd", 54 | "name" : "uma_authorization", 55 | "composite" : false, 56 | "clientRole" : false, 57 | "containerId" : "acme", 58 | "attributes" : { } 59 | } ], 60 | "client" : { 61 | "realm-management" : [ { 62 | "id" : "15280cdc-23eb-44c7-8de0-8e70ec81abe9", 63 | "name" : "manage-events", 64 | "description" : "${role_manage-events}", 65 | "composite" : false, 66 | "clientRole" : true, 67 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 68 | "attributes" : { } 69 | }, { 70 | "id" : "f917ba91-8366-4a05-a4ab-38103de116bc", 71 | "name" : "view-authorization", 72 | "description" : "${role_view-authorization}", 73 | "composite" : false, 74 | "clientRole" : true, 75 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 76 | "attributes" : { } 77 | }, { 78 | "id" : "f1df51d0-f9b3-4f53-8b15-dea6d69dadf2", 79 | "name" : "view-realm", 80 | "description" : "${role_view-realm}", 81 | "composite" : false, 82 | "clientRole" : true, 83 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 84 | "attributes" : { } 85 | }, { 86 | "id" : "a4ce49bb-de88-4840-8623-30d39308fcd6", 87 | "name" : "view-clients", 88 | "description" : "${role_view-clients}", 89 | "composite" : true, 90 | "composites" : { 91 | "client" : { 92 | "realm-management" : [ "query-clients" ] 93 | } 94 | }, 95 | "clientRole" : true, 96 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 97 | "attributes" : { } 98 | }, { 99 | "id" : "b73eec62-8068-4e31-a5b0-074ee4f8b255", 100 | "name" : "manage-identity-providers", 101 | "description" : "${role_manage-identity-providers}", 102 | "composite" : false, 103 | "clientRole" : true, 104 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 105 | "attributes" : { } 106 | }, { 107 | "id" : "18817174-ffba-4154-a06e-d75557640896", 108 | "name" : "view-identity-providers", 109 | "description" : "${role_view-identity-providers}", 110 | "composite" : false, 111 | "clientRole" : true, 112 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 113 | "attributes" : { } 114 | }, { 115 | "id" : "06dc5a0c-c854-4da9-8d7e-a6d12857e7b9", 116 | "name" : "query-groups", 117 | "description" : "${role_query-groups}", 118 | "composite" : false, 119 | "clientRole" : true, 120 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 121 | "attributes" : { } 122 | }, { 123 | "id" : "93b5097d-56cb-4576-b24d-f826fba239ab", 124 | "name" : "query-clients", 125 | "description" : "${role_query-clients}", 126 | "composite" : false, 127 | "clientRole" : true, 128 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 129 | "attributes" : { } 130 | }, { 131 | "id" : "183227f9-6a1b-4afc-b53b-05737e8f0deb", 132 | "name" : "manage-authorization", 133 | "description" : "${role_manage-authorization}", 134 | "composite" : false, 135 | "clientRole" : true, 136 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 137 | "attributes" : { } 138 | }, { 139 | "id" : "1567536d-40c6-4fcd-8b47-bbed91857023", 140 | "name" : "manage-users", 141 | "description" : "${role_manage-users}", 142 | "composite" : false, 143 | "clientRole" : true, 144 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 145 | "attributes" : { } 146 | }, { 147 | "id" : "2e1e7aaf-6b22-442d-bb86-fc04cc4f7eb9", 148 | "name" : "view-users", 149 | "description" : "${role_view-users}", 150 | "composite" : true, 151 | "composites" : { 152 | "client" : { 153 | "realm-management" : [ "query-groups", "query-users" ] 154 | } 155 | }, 156 | "clientRole" : true, 157 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 158 | "attributes" : { } 159 | }, { 160 | "id" : "7ead49d9-90a4-49f3-8cde-9b79f49b6584", 161 | "name" : "query-realms", 162 | "description" : "${role_query-realms}", 163 | "composite" : false, 164 | "clientRole" : true, 165 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 166 | "attributes" : { } 167 | }, { 168 | "id" : "cc174725-6139-4c4c-9230-d60e7477feaf", 169 | "name" : "manage-clients", 170 | "description" : "${role_manage-clients}", 171 | "composite" : false, 172 | "clientRole" : true, 173 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 174 | "attributes" : { } 175 | }, { 176 | "id" : "c7a3a11e-5b14-45d1-9d24-b85ae83911b2", 177 | "name" : "impersonation", 178 | "description" : "${role_impersonation}", 179 | "composite" : false, 180 | "clientRole" : true, 181 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 182 | "attributes" : { } 183 | }, { 184 | "id" : "511c0a3f-6f4f-4be1-9adf-3fe3ca7d7007", 185 | "name" : "realm-admin", 186 | "description" : "${role_realm-admin}", 187 | "composite" : true, 188 | "composites" : { 189 | "client" : { 190 | "realm-management" : [ "manage-events", "view-authorization", "view-realm", "view-clients", "manage-identity-providers", "view-identity-providers", "query-groups", "query-clients", "manage-users", "manage-authorization", "view-users", "query-realms", "manage-clients", "view-events", "impersonation", "create-client", "manage-realm", "query-users" ] 191 | } 192 | }, 193 | "clientRole" : true, 194 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 195 | "attributes" : { } 196 | }, { 197 | "id" : "933c120c-959a-4509-8ab3-3619cd3fd26b", 198 | "name" : "view-events", 199 | "description" : "${role_view-events}", 200 | "composite" : false, 201 | "clientRole" : true, 202 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 203 | "attributes" : { } 204 | }, { 205 | "id" : "2d356fb9-23c5-4db0-a63c-b211e7c23595", 206 | "name" : "create-client", 207 | "description" : "${role_create-client}", 208 | "composite" : false, 209 | "clientRole" : true, 210 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 211 | "attributes" : { } 212 | }, { 213 | "id" : "5425cf69-ae05-4ab7-8180-bbda860f2f48", 214 | "name" : "manage-realm", 215 | "description" : "${role_manage-realm}", 216 | "composite" : false, 217 | "clientRole" : true, 218 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 219 | "attributes" : { } 220 | }, { 221 | "id" : "79683db1-c3a5-472e-b370-e2e9d1d98d39", 222 | "name" : "query-users", 223 | "description" : "${role_query-users}", 224 | "composite" : false, 225 | "clientRole" : true, 226 | "containerId" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 227 | "attributes" : { } 228 | } ], 229 | "app-minispa" : [ ], 230 | "security-admin-console" : [ ], 231 | "admin-cli" : [ ], 232 | "test-client" : [ ], 233 | "account-console" : [ ], 234 | "broker" : [ ], 235 | "account" : [ { 236 | "id" : "0fa469d0-6348-4b48-b10e-cebc4f22f9b5", 237 | "name" : "manage-account", 238 | "composite" : false, 239 | "clientRole" : true, 240 | "containerId" : "b3fa51ab-d818-4221-b5aa-4a6ea7a31d7d", 241 | "attributes" : { } 242 | }, { 243 | "id" : "51ff6b55-e881-4e54-a4a8-45febd80ad49", 244 | "name" : "view-profile", 245 | "composite" : false, 246 | "clientRole" : true, 247 | "containerId" : "b3fa51ab-d818-4221-b5aa-4a6ea7a31d7d", 248 | "attributes" : { } 249 | }, { 250 | "id" : "c4ab549c-4f9a-4d3e-9596-d8a2e30534b8", 251 | "name" : "delete-account", 252 | "description" : "${role_delete-account}", 253 | "composite" : false, 254 | "clientRole" : true, 255 | "containerId" : "b3fa51ab-d818-4221-b5aa-4a6ea7a31d7d", 256 | "attributes" : { } 257 | } ], 258 | "app-demospa" : [ ] 259 | } 260 | }, 261 | "groups" : [ ], 262 | "defaultRoles" : [ "offline_access", "uma_authorization" ], 263 | "requiredCredentials" : [ "password" ], 264 | "passwordPolicy" : "hashIterations(5)", 265 | "otpPolicyType" : "totp", 266 | "otpPolicyAlgorithm" : "HmacSHA1", 267 | "otpPolicyInitialCounter" : 0, 268 | "otpPolicyDigits" : 6, 269 | "otpPolicyLookAheadWindow" : 1, 270 | "otpPolicyPeriod" : 30, 271 | "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], 272 | "webAuthnPolicyRpEntityName" : "keycloak", 273 | "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], 274 | "webAuthnPolicyRpId" : "", 275 | "webAuthnPolicyAttestationConveyancePreference" : "not specified", 276 | "webAuthnPolicyAuthenticatorAttachment" : "not specified", 277 | "webAuthnPolicyRequireResidentKey" : "not specified", 278 | "webAuthnPolicyUserVerificationRequirement" : "not specified", 279 | "webAuthnPolicyCreateTimeout" : 0, 280 | "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, 281 | "webAuthnPolicyAcceptableAaguids" : [ ], 282 | "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", 283 | "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], 284 | "webAuthnPolicyPasswordlessRpId" : "", 285 | "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", 286 | "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", 287 | "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", 288 | "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", 289 | "webAuthnPolicyPasswordlessCreateTimeout" : 0, 290 | "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, 291 | "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], 292 | "users" : [ { 293 | "id" : "ea6c1a8d-8e27-4ed3-bf48-577d264b7d59", 294 | "createdTimestamp" : 1617652894097, 295 | "username" : "test-user-age22", 296 | "enabled" : true, 297 | "totp" : false, 298 | "emailVerified" : false, 299 | "firstName" : "Firstname", 300 | "lastName" : "Lastname", 301 | "attributes" : { 302 | "birthdate" : [ "1999-04-05" ] 303 | }, 304 | "credentials" : [ { 305 | "id" : "5faf653a-517c-43e1-8739-f980f5092ccb", 306 | "type" : "password", 307 | "createdDate" : 1617656945687, 308 | "secretData" : "{\"value\":\"fucNCd7FNawqwHNBH6zHBUuItdcdZQZD5Lolo4AA9Dw+ixio0MWd7qEHc9iAZhlVgdPd7yGe9QkF0rbocxIQDQ==\",\"salt\":\"axBIzt79iBL5FiTYqeu3Ag==\",\"additionalParameters\":{}}", 309 | "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" 310 | } ], 311 | "disableableCredentialTypes" : [ ], 312 | "requiredActions" : [ ], 313 | "realmRoles" : [ "offline_access", "uma_authorization" ], 314 | "clientRoles" : { 315 | "account" : [ "manage-account", "view-profile" ] 316 | }, 317 | "notBefore" : 0, 318 | "groups" : [ ] 319 | }, { 320 | "id" : "bf77ad09-3d60-4ee2-bbcc-3f74965336dc", 321 | "createdTimestamp" : 1617641449258, 322 | "username" : "tester", 323 | "enabled" : true, 324 | "totp" : false, 325 | "emailVerified" : false, 326 | "firstName" : "Theo", 327 | "lastName" : "Tester", 328 | "email" : "tester@localhost", 329 | "attributes" : { 330 | "birthdate" : [ "1984-12-13" ] 331 | }, 332 | "credentials" : [ { 333 | "id" : "33342a2d-8757-4fad-9d7e-aa2fdd91a180", 334 | "type" : "password", 335 | "createdDate" : 1617659515087, 336 | "secretData" : "{\"value\":\"oeXgsFRRtdauTBSmrDMesCZWwABzWnSnsgWYsMohll6wTbiEfJhAnFaaQ6L6E8yx8Hqmkkk/yoWJ+rUrNKvhMQ==\",\"salt\":\"aaunx54jHMlDtiGHLXuLyQ==\",\"additionalParameters\":{}}", 337 | "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" 338 | } ], 339 | "disableableCredentialTypes" : [ ], 340 | "requiredActions" : [ ], 341 | "realmRoles" : [ "offline_access", "uma_authorization" ], 342 | "clientRoles" : { 343 | "account" : [ "manage-account", "view-profile" ] 344 | }, 345 | "notBefore" : 0, 346 | "groups" : [ ] 347 | } ], 348 | "scopeMappings" : [ { 349 | "clientScope" : "offline_access", 350 | "roles" : [ "offline_access" ] 351 | } ], 352 | "clientScopeMappings" : { 353 | "account" : [ { 354 | "client" : "account-console", 355 | "roles" : [ "manage-account" ] 356 | } ] 357 | }, 358 | "clients" : [ { 359 | "id" : "b3fa51ab-d818-4221-b5aa-4a6ea7a31d7d", 360 | "clientId" : "account", 361 | "name" : "${client_account}", 362 | "rootUrl" : "${authBaseUrl}", 363 | "baseUrl" : "/realms/acme/account/", 364 | "surrogateAuthRequired" : false, 365 | "enabled" : true, 366 | "alwaysDisplayInConsole" : false, 367 | "clientAuthenticatorType" : "client-secret", 368 | "secret" : "**********", 369 | "defaultRoles" : [ "manage-account", "view-profile" ], 370 | "redirectUris" : [ "/realms/acme/account/*" ], 371 | "webOrigins" : [ ], 372 | "notBefore" : 0, 373 | "bearerOnly" : false, 374 | "consentRequired" : false, 375 | "standardFlowEnabled" : true, 376 | "implicitFlowEnabled" : false, 377 | "directAccessGrantsEnabled" : false, 378 | "serviceAccountsEnabled" : false, 379 | "publicClient" : false, 380 | "frontchannelLogout" : false, 381 | "protocol" : "openid-connect", 382 | "attributes" : { }, 383 | "authenticationFlowBindingOverrides" : { }, 384 | "fullScopeAllowed" : false, 385 | "nodeReRegistrationTimeout" : 0, 386 | "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], 387 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 388 | }, { 389 | "id" : "f16c3bb1-f61b-4740-bcc7-bc65a9f7f055", 390 | "clientId" : "account-console", 391 | "name" : "${client_account-console}", 392 | "rootUrl" : "${authBaseUrl}", 393 | "baseUrl" : "/realms/acme/account/", 394 | "surrogateAuthRequired" : false, 395 | "enabled" : true, 396 | "alwaysDisplayInConsole" : false, 397 | "clientAuthenticatorType" : "client-secret", 398 | "secret" : "**********", 399 | "redirectUris" : [ "/realms/acme/account/*" ], 400 | "webOrigins" : [ ], 401 | "notBefore" : 0, 402 | "bearerOnly" : false, 403 | "consentRequired" : false, 404 | "standardFlowEnabled" : true, 405 | "implicitFlowEnabled" : false, 406 | "directAccessGrantsEnabled" : false, 407 | "serviceAccountsEnabled" : false, 408 | "publicClient" : true, 409 | "frontchannelLogout" : false, 410 | "protocol" : "openid-connect", 411 | "attributes" : { 412 | "pkce.code.challenge.method" : "S256" 413 | }, 414 | "authenticationFlowBindingOverrides" : { }, 415 | "fullScopeAllowed" : false, 416 | "nodeReRegistrationTimeout" : 0, 417 | "protocolMappers" : [ { 418 | "id" : "1721fc2d-249e-4ba5-837b-d7fb5e25c0f4", 419 | "name" : "audience resolve", 420 | "protocol" : "openid-connect", 421 | "protocolMapper" : "oidc-audience-resolve-mapper", 422 | "consentRequired" : false, 423 | "config" : { } 424 | } ], 425 | "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], 426 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 427 | }, { 428 | "id" : "6b3fd885-8031-4498-a351-b5ce1d0659b6", 429 | "clientId" : "admin-cli", 430 | "name" : "${client_admin-cli}", 431 | "surrogateAuthRequired" : false, 432 | "enabled" : true, 433 | "alwaysDisplayInConsole" : false, 434 | "clientAuthenticatorType" : "client-secret", 435 | "secret" : "**********", 436 | "redirectUris" : [ ], 437 | "webOrigins" : [ ], 438 | "notBefore" : 0, 439 | "bearerOnly" : false, 440 | "consentRequired" : false, 441 | "standardFlowEnabled" : false, 442 | "implicitFlowEnabled" : false, 443 | "directAccessGrantsEnabled" : true, 444 | "serviceAccountsEnabled" : false, 445 | "publicClient" : true, 446 | "frontchannelLogout" : false, 447 | "protocol" : "openid-connect", 448 | "attributes" : { }, 449 | "authenticationFlowBindingOverrides" : { }, 450 | "fullScopeAllowed" : false, 451 | "nodeReRegistrationTimeout" : 0, 452 | "defaultClientScopes" : [ "role_list", "roles", "profile", "email" ], 453 | "optionalClientScopes" : [ "address", "phone", "acme.ageinfo", "offline_access", "microprofile-jwt", "acme.profile" ] 454 | }, { 455 | "id" : "7d7a03ee-793f-4669-bd75-6f4741d4b0e5", 456 | "clientId" : "app-demospa", 457 | "name" : "Demo SPA", 458 | "rootUrl" : "http://demo.local:4000", 459 | "adminUrl" : "", 460 | "baseUrl" : "/?client_id=app-demospa", 461 | "surrogateAuthRequired" : false, 462 | "enabled" : true, 463 | "alwaysDisplayInConsole" : false, 464 | "clientAuthenticatorType" : "client-secret", 465 | "secret" : "**********", 466 | "redirectUris" : [ "/*" ], 467 | "webOrigins" : [ "+" ], 468 | "notBefore" : 0, 469 | "bearerOnly" : false, 470 | "consentRequired" : false, 471 | "standardFlowEnabled" : true, 472 | "implicitFlowEnabled" : false, 473 | "directAccessGrantsEnabled" : false, 474 | "serviceAccountsEnabled" : false, 475 | "publicClient" : true, 476 | "frontchannelLogout" : false, 477 | "protocol" : "openid-connect", 478 | "attributes" : { 479 | "saml.assertion.signature" : "false", 480 | "saml.force.post.binding" : "false", 481 | "saml.multivalued.roles" : "false", 482 | "saml.encrypt" : "false", 483 | "backchannel.logout.revoke.offline.tokens" : "false", 484 | "saml.server.signature" : "false", 485 | "saml.server.signature.keyinfo.ext" : "false", 486 | "exclude.session.state.from.auth.response" : "false", 487 | "backchannel.logout.session.required" : "true", 488 | "client_credentials.use_refresh_token" : "false", 489 | "saml_force_name_id_format" : "false", 490 | "saml.client.signature" : "false", 491 | "tls.client.certificate.bound.access.tokens" : "false", 492 | "saml.authnstatement" : "false", 493 | "display.on.consent.screen" : "false", 494 | "saml.onetimeuse.condition" : "false" 495 | }, 496 | "authenticationFlowBindingOverrides" : { }, 497 | "fullScopeAllowed" : false, 498 | "nodeReRegistrationTimeout" : -1, 499 | "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], 500 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 501 | }, { 502 | "id" : "f0bbf46f-2b1b-4dd3-a4d2-23fd4a0d3823", 503 | "clientId" : "app-minispa", 504 | "name" : "Mni SPA", 505 | "rootUrl" : "http://localhost:4000", 506 | "adminUrl" : "", 507 | "baseUrl" : "/", 508 | "surrogateAuthRequired" : false, 509 | "enabled" : true, 510 | "alwaysDisplayInConsole" : false, 511 | "clientAuthenticatorType" : "client-secret", 512 | "secret" : "**********", 513 | "redirectUris" : [ "/*" ], 514 | "webOrigins" : [ "+" ], 515 | "notBefore" : 0, 516 | "bearerOnly" : false, 517 | "consentRequired" : false, 518 | "standardFlowEnabled" : true, 519 | "implicitFlowEnabled" : false, 520 | "directAccessGrantsEnabled" : false, 521 | "serviceAccountsEnabled" : false, 522 | "publicClient" : true, 523 | "frontchannelLogout" : false, 524 | "protocol" : "openid-connect", 525 | "attributes" : { 526 | "saml.assertion.signature" : "false", 527 | "saml.force.post.binding" : "false", 528 | "saml.multivalued.roles" : "false", 529 | "saml.encrypt" : "false", 530 | "backchannel.logout.revoke.offline.tokens" : "false", 531 | "saml.server.signature" : "false", 532 | "saml.server.signature.keyinfo.ext" : "false", 533 | "exclude.session.state.from.auth.response" : "false", 534 | "backchannel.logout.session.required" : "false", 535 | "client_credentials.use_refresh_token" : "false", 536 | "saml_force_name_id_format" : "false", 537 | "saml.client.signature" : "false", 538 | "tls.client.certificate.bound.access.tokens" : "false", 539 | "saml.authnstatement" : "false", 540 | "display.on.consent.screen" : "false", 541 | "saml.onetimeuse.condition" : "false" 542 | }, 543 | "authenticationFlowBindingOverrides" : { }, 544 | "fullScopeAllowed" : false, 545 | "nodeReRegistrationTimeout" : -1, 546 | "defaultClientScopes" : [ "role_list", "email" ], 547 | "optionalClientScopes" : [ "acme.ageinfo", "phone", "acme.profile" ] 548 | }, { 549 | "id" : "d827cb0d-4d73-400a-a5c3-9f89eedb1c41", 550 | "clientId" : "broker", 551 | "name" : "${client_broker}", 552 | "surrogateAuthRequired" : false, 553 | "enabled" : true, 554 | "alwaysDisplayInConsole" : false, 555 | "clientAuthenticatorType" : "client-secret", 556 | "secret" : "**********", 557 | "redirectUris" : [ ], 558 | "webOrigins" : [ ], 559 | "notBefore" : 0, 560 | "bearerOnly" : false, 561 | "consentRequired" : false, 562 | "standardFlowEnabled" : true, 563 | "implicitFlowEnabled" : false, 564 | "directAccessGrantsEnabled" : false, 565 | "serviceAccountsEnabled" : false, 566 | "publicClient" : false, 567 | "frontchannelLogout" : false, 568 | "protocol" : "openid-connect", 569 | "attributes" : { }, 570 | "authenticationFlowBindingOverrides" : { }, 571 | "fullScopeAllowed" : false, 572 | "nodeReRegistrationTimeout" : 0, 573 | "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], 574 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 575 | }, { 576 | "id" : "a28f054a-e493-4bbf-b361-e2ac4746467a", 577 | "clientId" : "realm-management", 578 | "name" : "${client_realm-management}", 579 | "surrogateAuthRequired" : false, 580 | "enabled" : true, 581 | "alwaysDisplayInConsole" : false, 582 | "clientAuthenticatorType" : "client-secret", 583 | "secret" : "**********", 584 | "redirectUris" : [ ], 585 | "webOrigins" : [ ], 586 | "notBefore" : 0, 587 | "bearerOnly" : true, 588 | "consentRequired" : false, 589 | "standardFlowEnabled" : true, 590 | "implicitFlowEnabled" : false, 591 | "directAccessGrantsEnabled" : false, 592 | "serviceAccountsEnabled" : false, 593 | "publicClient" : false, 594 | "frontchannelLogout" : false, 595 | "protocol" : "openid-connect", 596 | "attributes" : { }, 597 | "authenticationFlowBindingOverrides" : { }, 598 | "fullScopeAllowed" : false, 599 | "nodeReRegistrationTimeout" : 0, 600 | "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], 601 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 602 | }, { 603 | "id" : "4f70f1f5-4776-4c06-9fc5-ca32ce890649", 604 | "clientId" : "security-admin-console", 605 | "name" : "${client_security-admin-console}", 606 | "rootUrl" : "${authAdminUrl}", 607 | "baseUrl" : "/admin/acme/console/", 608 | "surrogateAuthRequired" : false, 609 | "enabled" : true, 610 | "alwaysDisplayInConsole" : false, 611 | "clientAuthenticatorType" : "client-secret", 612 | "secret" : "**********", 613 | "redirectUris" : [ "/admin/acme/console/*" ], 614 | "webOrigins" : [ "+" ], 615 | "notBefore" : 0, 616 | "bearerOnly" : false, 617 | "consentRequired" : false, 618 | "standardFlowEnabled" : true, 619 | "implicitFlowEnabled" : false, 620 | "directAccessGrantsEnabled" : false, 621 | "serviceAccountsEnabled" : false, 622 | "publicClient" : true, 623 | "frontchannelLogout" : false, 624 | "protocol" : "openid-connect", 625 | "attributes" : { 626 | "pkce.code.challenge.method" : "S256" 627 | }, 628 | "authenticationFlowBindingOverrides" : { }, 629 | "fullScopeAllowed" : false, 630 | "nodeReRegistrationTimeout" : 0, 631 | "protocolMappers" : [ { 632 | "id" : "2f459275-4d52-4cc7-b97a-93bd14dfc87b", 633 | "name" : "locale", 634 | "protocol" : "openid-connect", 635 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 636 | "consentRequired" : false, 637 | "config" : { 638 | "userinfo.token.claim" : "true", 639 | "user.attribute" : "locale", 640 | "id.token.claim" : "true", 641 | "access.token.claim" : "true", 642 | "claim.name" : "locale", 643 | "jsonType.label" : "String" 644 | } 645 | } ], 646 | "defaultClientScopes" : [ "web-origins", "role_list", "profile", "roles", "email" ], 647 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 648 | }, { 649 | "id" : "3e52ddce-338c-4e42-87a9-840a0b6a31cf", 650 | "clientId" : "test-client", 651 | "surrogateAuthRequired" : false, 652 | "enabled" : true, 653 | "alwaysDisplayInConsole" : false, 654 | "clientAuthenticatorType" : "client-secret", 655 | "secret" : "3f455bd5-9495-41f8-9085-83ccebcc95f2", 656 | "redirectUris" : [ ], 657 | "webOrigins" : [ ], 658 | "notBefore" : 0, 659 | "bearerOnly" : false, 660 | "consentRequired" : false, 661 | "standardFlowEnabled" : false, 662 | "implicitFlowEnabled" : false, 663 | "directAccessGrantsEnabled" : true, 664 | "serviceAccountsEnabled" : false, 665 | "publicClient" : true, 666 | "frontchannelLogout" : false, 667 | "protocol" : "openid-connect", 668 | "attributes" : { 669 | "saml.assertion.signature" : "false", 670 | "access.token.lifespan" : "10800", 671 | "saml.force.post.binding" : "false", 672 | "saml.multivalued.roles" : "false", 673 | "saml.encrypt" : "false", 674 | "backchannel.logout.revoke.offline.tokens" : "false", 675 | "saml.server.signature" : "false", 676 | "saml.server.signature.keyinfo.ext" : "false", 677 | "exclude.session.state.from.auth.response" : "false", 678 | "backchannel.logout.session.required" : "false", 679 | "client_credentials.use_refresh_token" : "false", 680 | "saml_force_name_id_format" : "false", 681 | "saml.client.signature" : "false", 682 | "tls.client.certificate.bound.access.tokens" : "false", 683 | "saml.authnstatement" : "false", 684 | "display.on.consent.screen" : "false", 685 | "saml.onetimeuse.condition" : "false" 686 | }, 687 | "authenticationFlowBindingOverrides" : { }, 688 | "fullScopeAllowed" : true, 689 | "nodeReRegistrationTimeout" : -1, 690 | "defaultClientScopes" : [ "role_list", "acme.api", "email" ], 691 | "optionalClientScopes" : [ "address", "phone", "acme.ageinfo", "acme.profile" ] 692 | } ], 693 | "clientScopes" : [ { 694 | "id" : "d1fcb261-d1d3-4158-b540-f0650fe52467", 695 | "name" : "acme.ageinfo", 696 | "description" : "Age Information", 697 | "protocol" : "openid-connect", 698 | "attributes" : { 699 | "include.in.token.scope" : "true", 700 | "display.on.consent.screen" : "true" 701 | }, 702 | "protocolMappers" : [ { 703 | "id" : "451ff95b-bb55-4745-bd49-7d357a9310b3", 704 | "name" : "Age Info Claim Mapping", 705 | "protocol" : "openid-connect", 706 | "protocolMapper" : "acme-ageinfo-mapper", 707 | "consentRequired" : false, 708 | "config" : { 709 | "id.token.claim" : "true", 710 | "access.token.claim" : "false", 711 | "userinfo.token.claim" : "true" 712 | } 713 | } ] 714 | }, { 715 | "id" : "899aaa48-2e24-45a5-b7d6-9ab8102f9dd9", 716 | "name" : "acme.api", 717 | "description" : "Acme API Access", 718 | "protocol" : "openid-connect", 719 | "attributes" : { 720 | "include.in.token.scope" : "true", 721 | "display.on.consent.screen" : "true" 722 | } 723 | }, { 724 | "id" : "d959ecfe-693e-4289-8cd3-722e4ddc3ac6", 725 | "name" : "acme.profile", 726 | "description" : "Acme Profile Information", 727 | "protocol" : "openid-connect", 728 | "attributes" : { 729 | "include.in.token.scope" : "true", 730 | "display.on.consent.screen" : "true" 731 | }, 732 | "protocolMappers" : [ { 733 | "id" : "7c1e76a9-2c08-45b6-a235-197ef1e9f1e2", 734 | "name" : "family name", 735 | "protocol" : "openid-connect", 736 | "protocolMapper" : "oidc-usermodel-property-mapper", 737 | "consentRequired" : false, 738 | "config" : { 739 | "userinfo.token.claim" : "true", 740 | "user.attribute" : "lastName", 741 | "id.token.claim" : "true", 742 | "access.token.claim" : "true", 743 | "claim.name" : "family_name", 744 | "jsonType.label" : "String" 745 | } 746 | }, { 747 | "id" : "b186b546-e46d-4e7d-bdec-329889b4fa03", 748 | "name" : "username", 749 | "protocol" : "openid-connect", 750 | "protocolMapper" : "oidc-usermodel-property-mapper", 751 | "consentRequired" : false, 752 | "config" : { 753 | "userinfo.token.claim" : "true", 754 | "user.attribute" : "username", 755 | "id.token.claim" : "true", 756 | "access.token.claim" : "true", 757 | "claim.name" : "preferred_username", 758 | "jsonType.label" : "String" 759 | } 760 | }, { 761 | "id" : "463183a6-a9b5-4bfd-9c93-b0237d26a194", 762 | "name" : "given name", 763 | "protocol" : "openid-connect", 764 | "protocolMapper" : "oidc-usermodel-property-mapper", 765 | "consentRequired" : false, 766 | "config" : { 767 | "userinfo.token.claim" : "true", 768 | "user.attribute" : "firstName", 769 | "id.token.claim" : "true", 770 | "access.token.claim" : "true", 771 | "claim.name" : "given_name", 772 | "jsonType.label" : "String" 773 | } 774 | }, { 775 | "id" : "ba5f7999-e7cf-40db-91c5-99175dad992e", 776 | "name" : "full name", 777 | "protocol" : "openid-connect", 778 | "protocolMapper" : "oidc-full-name-mapper", 779 | "consentRequired" : false, 780 | "config" : { 781 | "id.token.claim" : "true", 782 | "access.token.claim" : "true", 783 | "userinfo.token.claim" : "true" 784 | } 785 | } ] 786 | }, { 787 | "id" : "a28a09e8-a5f6-4337-820c-943ee574b419", 788 | "name" : "address", 789 | "description" : "OpenID Connect built-in scope: address", 790 | "protocol" : "openid-connect", 791 | "attributes" : { 792 | "include.in.token.scope" : "true", 793 | "display.on.consent.screen" : "true", 794 | "consent.screen.text" : "${addressScopeConsentText}" 795 | }, 796 | "protocolMappers" : [ { 797 | "id" : "630f2115-8add-4a6c-8aab-f5afbf69cb50", 798 | "name" : "address", 799 | "protocol" : "openid-connect", 800 | "protocolMapper" : "oidc-address-mapper", 801 | "consentRequired" : false, 802 | "config" : { 803 | "user.attribute.formatted" : "formatted", 804 | "user.attribute.country" : "country", 805 | "user.attribute.postal_code" : "postal_code", 806 | "userinfo.token.claim" : "true", 807 | "user.attribute.street" : "street", 808 | "id.token.claim" : "true", 809 | "user.attribute.region" : "region", 810 | "access.token.claim" : "true", 811 | "user.attribute.locality" : "locality" 812 | } 813 | } ] 814 | }, { 815 | "id" : "740fdfe3-57b1-46c6-b34f-d20a44d81f86", 816 | "name" : "email", 817 | "description" : "OpenID Connect built-in scope: email", 818 | "protocol" : "openid-connect", 819 | "attributes" : { 820 | "include.in.token.scope" : "true", 821 | "display.on.consent.screen" : "true", 822 | "consent.screen.text" : "${emailScopeConsentText}" 823 | }, 824 | "protocolMappers" : [ { 825 | "id" : "b6b2ebf2-1968-4d96-b617-dd9974ac3206", 826 | "name" : "email", 827 | "protocol" : "openid-connect", 828 | "protocolMapper" : "oidc-usermodel-property-mapper", 829 | "consentRequired" : false, 830 | "config" : { 831 | "userinfo.token.claim" : "true", 832 | "user.attribute" : "email", 833 | "id.token.claim" : "true", 834 | "access.token.claim" : "true", 835 | "claim.name" : "email", 836 | "jsonType.label" : "String" 837 | } 838 | }, { 839 | "id" : "15c34ab3-0c92-430d-b92c-f08f74da9ccd", 840 | "name" : "email verified", 841 | "protocol" : "openid-connect", 842 | "protocolMapper" : "oidc-usermodel-property-mapper", 843 | "consentRequired" : false, 844 | "config" : { 845 | "userinfo.token.claim" : "true", 846 | "user.attribute" : "emailVerified", 847 | "id.token.claim" : "true", 848 | "access.token.claim" : "true", 849 | "claim.name" : "email_verified", 850 | "jsonType.label" : "boolean" 851 | } 852 | } ] 853 | }, { 854 | "id" : "38617936-7698-403f-b786-e54383a28f0e", 855 | "name" : "microprofile-jwt", 856 | "description" : "Microprofile - JWT built-in scope", 857 | "protocol" : "openid-connect", 858 | "attributes" : { 859 | "include.in.token.scope" : "true", 860 | "display.on.consent.screen" : "false" 861 | }, 862 | "protocolMappers" : [ { 863 | "id" : "eca70bcc-04bb-46d4-a2b8-5af4859f27a6", 864 | "name" : "groups", 865 | "protocol" : "openid-connect", 866 | "protocolMapper" : "oidc-usermodel-realm-role-mapper", 867 | "consentRequired" : false, 868 | "config" : { 869 | "multivalued" : "true", 870 | "userinfo.token.claim" : "true", 871 | "user.attribute" : "foo", 872 | "id.token.claim" : "true", 873 | "access.token.claim" : "true", 874 | "claim.name" : "groups", 875 | "jsonType.label" : "String" 876 | } 877 | }, { 878 | "id" : "22575165-960f-4716-93cc-2dfc97377edb", 879 | "name" : "upn", 880 | "protocol" : "openid-connect", 881 | "protocolMapper" : "oidc-usermodel-property-mapper", 882 | "consentRequired" : false, 883 | "config" : { 884 | "userinfo.token.claim" : "true", 885 | "user.attribute" : "username", 886 | "id.token.claim" : "true", 887 | "access.token.claim" : "true", 888 | "claim.name" : "upn", 889 | "jsonType.label" : "String" 890 | } 891 | } ] 892 | }, { 893 | "id" : "86cab515-2542-4a88-a197-9fd4ce1a1578", 894 | "name" : "offline_access", 895 | "description" : "OpenID Connect built-in scope: offline_access", 896 | "protocol" : "openid-connect", 897 | "attributes" : { 898 | "consent.screen.text" : "${offlineAccessScopeConsentText}", 899 | "display.on.consent.screen" : "true" 900 | } 901 | }, { 902 | "id" : "e3756163-8afb-4ddd-8f88-a9e73ad51fcf", 903 | "name" : "phone", 904 | "description" : "OpenID Connect built-in scope: phone", 905 | "protocol" : "openid-connect", 906 | "attributes" : { 907 | "include.in.token.scope" : "true", 908 | "display.on.consent.screen" : "true", 909 | "consent.screen.text" : "${phoneScopeConsentText}" 910 | }, 911 | "protocolMappers" : [ { 912 | "id" : "1720c48e-4717-4443-bb7b-791ef87e9b62", 913 | "name" : "phone number", 914 | "protocol" : "openid-connect", 915 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 916 | "consentRequired" : false, 917 | "config" : { 918 | "userinfo.token.claim" : "true", 919 | "user.attribute" : "phoneNumber", 920 | "id.token.claim" : "true", 921 | "access.token.claim" : "true", 922 | "claim.name" : "phone_number", 923 | "jsonType.label" : "String" 924 | } 925 | }, { 926 | "id" : "8d80394f-d5b7-4e66-80e5-1fc815c165a2", 927 | "name" : "phone number verified", 928 | "protocol" : "openid-connect", 929 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 930 | "consentRequired" : false, 931 | "config" : { 932 | "userinfo.token.claim" : "true", 933 | "user.attribute" : "phoneNumberVerified", 934 | "id.token.claim" : "true", 935 | "access.token.claim" : "true", 936 | "claim.name" : "phone_number_verified", 937 | "jsonType.label" : "boolean" 938 | } 939 | } ] 940 | }, { 941 | "id" : "f6923454-f735-409e-832e-ba0160e4fd60", 942 | "name" : "profile", 943 | "description" : "OpenID Connect built-in scope: profile", 944 | "protocol" : "openid-connect", 945 | "attributes" : { 946 | "include.in.token.scope" : "true", 947 | "display.on.consent.screen" : "true", 948 | "consent.screen.text" : "${profileScopeConsentText}" 949 | }, 950 | "protocolMappers" : [ { 951 | "id" : "5ce0007d-6460-4e4f-8d43-dd1c3092a4ce", 952 | "name" : "locale", 953 | "protocol" : "openid-connect", 954 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 955 | "consentRequired" : false, 956 | "config" : { 957 | "userinfo.token.claim" : "true", 958 | "user.attribute" : "locale", 959 | "id.token.claim" : "true", 960 | "access.token.claim" : "true", 961 | "claim.name" : "locale", 962 | "jsonType.label" : "String" 963 | } 964 | }, { 965 | "id" : "591cb585-2eee-4875-94b5-ee242bc6d03b", 966 | "name" : "updated at", 967 | "protocol" : "openid-connect", 968 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 969 | "consentRequired" : false, 970 | "config" : { 971 | "userinfo.token.claim" : "true", 972 | "user.attribute" : "updatedAt", 973 | "id.token.claim" : "true", 974 | "access.token.claim" : "true", 975 | "claim.name" : "updated_at", 976 | "jsonType.label" : "String" 977 | } 978 | }, { 979 | "id" : "ed63103c-50da-4d65-9a5a-6ccc1d73c17f", 980 | "name" : "gender", 981 | "protocol" : "openid-connect", 982 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 983 | "consentRequired" : false, 984 | "config" : { 985 | "userinfo.token.claim" : "true", 986 | "user.attribute" : "gender", 987 | "id.token.claim" : "true", 988 | "access.token.claim" : "true", 989 | "claim.name" : "gender", 990 | "jsonType.label" : "String" 991 | } 992 | }, { 993 | "id" : "6fa5af50-3123-4cdb-b5f2-9d5e00124b13", 994 | "name" : "username", 995 | "protocol" : "openid-connect", 996 | "protocolMapper" : "oidc-usermodel-property-mapper", 997 | "consentRequired" : false, 998 | "config" : { 999 | "userinfo.token.claim" : "true", 1000 | "user.attribute" : "username", 1001 | "id.token.claim" : "true", 1002 | "access.token.claim" : "true", 1003 | "claim.name" : "preferred_username", 1004 | "jsonType.label" : "String" 1005 | } 1006 | }, { 1007 | "id" : "f0f521b3-0658-4d89-ae41-ae342d424dc2", 1008 | "name" : "full name", 1009 | "protocol" : "openid-connect", 1010 | "protocolMapper" : "oidc-full-name-mapper", 1011 | "consentRequired" : false, 1012 | "config" : { 1013 | "id.token.claim" : "true", 1014 | "access.token.claim" : "true", 1015 | "userinfo.token.claim" : "true" 1016 | } 1017 | }, { 1018 | "id" : "bf1e2b1f-7520-4231-a0c5-8e472d8607da", 1019 | "name" : "profile", 1020 | "protocol" : "openid-connect", 1021 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 1022 | "consentRequired" : false, 1023 | "config" : { 1024 | "userinfo.token.claim" : "true", 1025 | "user.attribute" : "profile", 1026 | "id.token.claim" : "true", 1027 | "access.token.claim" : "true", 1028 | "claim.name" : "profile", 1029 | "jsonType.label" : "String" 1030 | } 1031 | }, { 1032 | "id" : "4a9a972e-1fa3-4b85-a6c9-94d50af68bb4", 1033 | "name" : "given name", 1034 | "protocol" : "openid-connect", 1035 | "protocolMapper" : "oidc-usermodel-property-mapper", 1036 | "consentRequired" : false, 1037 | "config" : { 1038 | "userinfo.token.claim" : "true", 1039 | "user.attribute" : "firstName", 1040 | "id.token.claim" : "true", 1041 | "access.token.claim" : "true", 1042 | "claim.name" : "given_name", 1043 | "jsonType.label" : "String" 1044 | } 1045 | }, { 1046 | "id" : "85f13da7-7a32-4e13-8bf8-74fca83c1637", 1047 | "name" : "nickname", 1048 | "protocol" : "openid-connect", 1049 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 1050 | "consentRequired" : false, 1051 | "config" : { 1052 | "userinfo.token.claim" : "true", 1053 | "user.attribute" : "nickname", 1054 | "id.token.claim" : "true", 1055 | "access.token.claim" : "true", 1056 | "claim.name" : "nickname", 1057 | "jsonType.label" : "String" 1058 | } 1059 | }, { 1060 | "id" : "cfe31027-1996-410c-ad38-1307d73d33a4", 1061 | "name" : "zoneinfo", 1062 | "protocol" : "openid-connect", 1063 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 1064 | "consentRequired" : false, 1065 | "config" : { 1066 | "userinfo.token.claim" : "true", 1067 | "user.attribute" : "zoneinfo", 1068 | "id.token.claim" : "true", 1069 | "access.token.claim" : "true", 1070 | "claim.name" : "zoneinfo", 1071 | "jsonType.label" : "String" 1072 | } 1073 | }, { 1074 | "id" : "6d9fe48e-b10b-42cf-9da0-6db943d68ebb", 1075 | "name" : "picture", 1076 | "protocol" : "openid-connect", 1077 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 1078 | "consentRequired" : false, 1079 | "config" : { 1080 | "userinfo.token.claim" : "true", 1081 | "user.attribute" : "picture", 1082 | "id.token.claim" : "true", 1083 | "access.token.claim" : "true", 1084 | "claim.name" : "picture", 1085 | "jsonType.label" : "String" 1086 | } 1087 | }, { 1088 | "id" : "ceba7294-6ffc-46f9-959a-2467b88f958b", 1089 | "name" : "birthdate", 1090 | "protocol" : "openid-connect", 1091 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 1092 | "consentRequired" : false, 1093 | "config" : { 1094 | "userinfo.token.claim" : "true", 1095 | "user.attribute" : "birthdate", 1096 | "id.token.claim" : "true", 1097 | "access.token.claim" : "true", 1098 | "claim.name" : "birthdate", 1099 | "jsonType.label" : "String" 1100 | } 1101 | }, { 1102 | "id" : "7fb278cf-46ee-4b3e-a67c-d33fc2511e46", 1103 | "name" : "middle name", 1104 | "protocol" : "openid-connect", 1105 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 1106 | "consentRequired" : false, 1107 | "config" : { 1108 | "userinfo.token.claim" : "true", 1109 | "user.attribute" : "middleName", 1110 | "id.token.claim" : "true", 1111 | "access.token.claim" : "true", 1112 | "claim.name" : "middle_name", 1113 | "jsonType.label" : "String" 1114 | } 1115 | }, { 1116 | "id" : "1a18fa81-aa25-4841-8d2f-03da2b2d8335", 1117 | "name" : "website", 1118 | "protocol" : "openid-connect", 1119 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 1120 | "consentRequired" : false, 1121 | "config" : { 1122 | "userinfo.token.claim" : "true", 1123 | "user.attribute" : "website", 1124 | "id.token.claim" : "true", 1125 | "access.token.claim" : "true", 1126 | "claim.name" : "website", 1127 | "jsonType.label" : "String" 1128 | } 1129 | }, { 1130 | "id" : "5eade531-7a8a-4ea8-bddd-14610fe359e0", 1131 | "name" : "family name", 1132 | "protocol" : "openid-connect", 1133 | "protocolMapper" : "oidc-usermodel-property-mapper", 1134 | "consentRequired" : false, 1135 | "config" : { 1136 | "userinfo.token.claim" : "true", 1137 | "user.attribute" : "lastName", 1138 | "id.token.claim" : "true", 1139 | "access.token.claim" : "true", 1140 | "claim.name" : "family_name", 1141 | "jsonType.label" : "String" 1142 | } 1143 | } ] 1144 | }, { 1145 | "id" : "4e3c2cdc-f0be-4328-b9d2-181864aeefb0", 1146 | "name" : "role_list", 1147 | "description" : "SAML role list", 1148 | "protocol" : "saml", 1149 | "attributes" : { 1150 | "consent.screen.text" : "${samlRoleListScopeConsentText}", 1151 | "display.on.consent.screen" : "true" 1152 | }, 1153 | "protocolMappers" : [ { 1154 | "id" : "72319e95-e9dd-4e33-b61c-28dde2ddefe3", 1155 | "name" : "role list", 1156 | "protocol" : "saml", 1157 | "protocolMapper" : "saml-role-list-mapper", 1158 | "consentRequired" : false, 1159 | "config" : { 1160 | "single" : "false", 1161 | "attribute.nameformat" : "Basic", 1162 | "attribute.name" : "Role" 1163 | } 1164 | } ] 1165 | }, { 1166 | "id" : "f6f9d855-5921-4163-9394-68886511bfe9", 1167 | "name" : "roles", 1168 | "description" : "OpenID Connect scope for add user roles to the access token", 1169 | "protocol" : "openid-connect", 1170 | "attributes" : { 1171 | "include.in.token.scope" : "false", 1172 | "display.on.consent.screen" : "true", 1173 | "consent.screen.text" : "${rolesScopeConsentText}" 1174 | }, 1175 | "protocolMappers" : [ { 1176 | "id" : "e4a6683d-b42d-472c-9590-6614a8ffecd2", 1177 | "name" : "realm roles", 1178 | "protocol" : "openid-connect", 1179 | "protocolMapper" : "oidc-usermodel-realm-role-mapper", 1180 | "consentRequired" : false, 1181 | "config" : { 1182 | "user.attribute" : "foo", 1183 | "access.token.claim" : "true", 1184 | "claim.name" : "realm_access.roles", 1185 | "jsonType.label" : "String", 1186 | "multivalued" : "true" 1187 | } 1188 | }, { 1189 | "id" : "d016c9d0-e8f9-4ec5-97f3-5a3a4bec4956", 1190 | "name" : "client roles", 1191 | "protocol" : "openid-connect", 1192 | "protocolMapper" : "oidc-usermodel-client-role-mapper", 1193 | "consentRequired" : false, 1194 | "config" : { 1195 | "user.attribute" : "foo", 1196 | "access.token.claim" : "true", 1197 | "claim.name" : "resource_access.${client_id}.roles", 1198 | "jsonType.label" : "String", 1199 | "multivalued" : "true" 1200 | } 1201 | }, { 1202 | "id" : "7c6d3738-194e-4a23-abca-442a0b183ad2", 1203 | "name" : "audience resolve", 1204 | "protocol" : "openid-connect", 1205 | "protocolMapper" : "oidc-audience-resolve-mapper", 1206 | "consentRequired" : false, 1207 | "config" : { } 1208 | } ] 1209 | }, { 1210 | "id" : "437cf929-79ce-4ab3-b116-e293294c16b1", 1211 | "name" : "web-origins", 1212 | "description" : "OpenID Connect scope for add allowed web origins to the access token", 1213 | "protocol" : "openid-connect", 1214 | "attributes" : { 1215 | "include.in.token.scope" : "false", 1216 | "display.on.consent.screen" : "false", 1217 | "consent.screen.text" : "" 1218 | }, 1219 | "protocolMappers" : [ { 1220 | "id" : "ec4848c9-1fe8-4013-b377-d91fa7703b19", 1221 | "name" : "allowed web origins", 1222 | "protocol" : "openid-connect", 1223 | "protocolMapper" : "oidc-allowed-origins-mapper", 1224 | "consentRequired" : false, 1225 | "config" : { } 1226 | } ] 1227 | } ], 1228 | "defaultDefaultClientScopes" : [ "web-origins", "role_list", "email", "profile", "roles", "acme.api" ], 1229 | "defaultOptionalClientScopes" : [ "microprofile-jwt", "offline_access", "address", "phone" ], 1230 | "browserSecurityHeaders" : { 1231 | "contentSecurityPolicyReportOnly" : "", 1232 | "xContentTypeOptions" : "nosniff", 1233 | "xRobotsTag" : "none", 1234 | "xFrameOptions" : "SAMEORIGIN", 1235 | "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", 1236 | "xXSSProtection" : "1; mode=block", 1237 | "strictTransportSecurity" : "max-age=31536000; includeSubDomains" 1238 | }, 1239 | "smtpServer" : { }, 1240 | "eventsEnabled" : false, 1241 | "eventsListeners" : [ "jboss-logging", "acme-audit-listener" ], 1242 | "enabledEventTypes" : [ "SEND_RESET_PASSWORD", "UPDATE_CONSENT_ERROR", "GRANT_CONSENT", "REMOVE_TOTP", "REVOKE_GRANT", "UPDATE_TOTP", "LOGIN_ERROR", "CLIENT_LOGIN", "RESET_PASSWORD_ERROR", "IMPERSONATE_ERROR", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "RESTART_AUTHENTICATION", "IMPERSONATE", "UPDATE_PROFILE_ERROR", "LOGIN", "UPDATE_PASSWORD_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "TOKEN_EXCHANGE", "LOGOUT", "REGISTER", "DELETE_ACCOUNT_ERROR", "CLIENT_REGISTER", "IDENTITY_PROVIDER_LINK_ACCOUNT", "DELETE_ACCOUNT", "UPDATE_PASSWORD", "CLIENT_DELETE", "FEDERATED_IDENTITY_LINK_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "CLIENT_DELETE_ERROR", "VERIFY_EMAIL", "CLIENT_LOGIN_ERROR", "RESTART_AUTHENTICATION_ERROR", "EXECUTE_ACTIONS", "REMOVE_FEDERATED_IDENTITY_ERROR", "TOKEN_EXCHANGE_ERROR", "PERMISSION_TOKEN", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "EXECUTE_ACTION_TOKEN_ERROR", "SEND_VERIFY_EMAIL", "EXECUTE_ACTIONS_ERROR", "REMOVE_FEDERATED_IDENTITY", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "UPDATE_EMAIL", "REGISTER_ERROR", "REVOKE_GRANT_ERROR", "EXECUTE_ACTION_TOKEN", "LOGOUT_ERROR", "UPDATE_EMAIL_ERROR", "CLIENT_UPDATE_ERROR", "UPDATE_PROFILE", "CLIENT_REGISTER_ERROR", "FEDERATED_IDENTITY_LINK", "SEND_IDENTITY_PROVIDER_LINK", "SEND_VERIFY_EMAIL_ERROR", "RESET_PASSWORD", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "UPDATE_CONSENT", "REMOVE_TOTP_ERROR", "VERIFY_EMAIL_ERROR", "SEND_RESET_PASSWORD_ERROR", "CLIENT_UPDATE", "CUSTOM_REQUIRED_ACTION_ERROR", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "UPDATE_TOTP_ERROR", "CODE_TO_TOKEN", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR" ], 1243 | "adminEventsEnabled" : false, 1244 | "adminEventsDetailsEnabled" : false, 1245 | "identityProviders" : [ ], 1246 | "identityProviderMappers" : [ ], 1247 | "components" : { 1248 | "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { 1249 | "id" : "302dd88c-3106-4452-a3a2-3945c8c13fc3", 1250 | "name" : "Full Scope Disabled", 1251 | "providerId" : "scope", 1252 | "subType" : "anonymous", 1253 | "subComponents" : { }, 1254 | "config" : { } 1255 | }, { 1256 | "id" : "0b4bb70e-3bf6-4a9f-b163-13e458f1cba6", 1257 | "name" : "Allowed Client Scopes", 1258 | "providerId" : "allowed-client-templates", 1259 | "subType" : "authenticated", 1260 | "subComponents" : { }, 1261 | "config" : { 1262 | "allow-default-scopes" : [ "true" ] 1263 | } 1264 | }, { 1265 | "id" : "8c2743ed-554c-4acd-92d3-5720a24f4465", 1266 | "name" : "Allowed Protocol Mapper Types", 1267 | "providerId" : "allowed-protocol-mappers", 1268 | "subType" : "authenticated", 1269 | "subComponents" : { }, 1270 | "config" : { 1271 | "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper" ] 1272 | } 1273 | }, { 1274 | "id" : "c1809668-78d3-470c-ac3f-ef415fb0c7fa", 1275 | "name" : "Max Clients Limit", 1276 | "providerId" : "max-clients", 1277 | "subType" : "anonymous", 1278 | "subComponents" : { }, 1279 | "config" : { 1280 | "max-clients" : [ "200" ] 1281 | } 1282 | }, { 1283 | "id" : "d239c378-be85-48fa-9639-2f6811416d46", 1284 | "name" : "Allowed Protocol Mapper Types", 1285 | "providerId" : "allowed-protocol-mappers", 1286 | "subType" : "anonymous", 1287 | "subComponents" : { }, 1288 | "config" : { 1289 | "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper" ] 1290 | } 1291 | }, { 1292 | "id" : "a1778593-d5fa-4105-8130-f0258941cdfb", 1293 | "name" : "Trusted Hosts", 1294 | "providerId" : "trusted-hosts", 1295 | "subType" : "anonymous", 1296 | "subComponents" : { }, 1297 | "config" : { 1298 | "host-sending-registration-request-must-match" : [ "true" ], 1299 | "client-uris-must-match" : [ "true" ] 1300 | } 1301 | }, { 1302 | "id" : "58a26f4f-a339-4d1f-bdef-4a83dab07362", 1303 | "name" : "Allowed Client Scopes", 1304 | "providerId" : "allowed-client-templates", 1305 | "subType" : "anonymous", 1306 | "subComponents" : { }, 1307 | "config" : { 1308 | "allow-default-scopes" : [ "true" ] 1309 | } 1310 | }, { 1311 | "id" : "5cca0e64-c2bd-4c64-a11a-8ded0cc09300", 1312 | "name" : "Consent Required", 1313 | "providerId" : "consent-required", 1314 | "subType" : "anonymous", 1315 | "subComponents" : { }, 1316 | "config" : { } 1317 | } ], 1318 | "org.keycloak.keys.KeyProvider" : [ { 1319 | "id" : "a1bbd6a6-7ba3-4eb7-a8ce-6b90aa3a433b", 1320 | "name" : "hmac-generated", 1321 | "providerId" : "hmac-generated", 1322 | "subComponents" : { }, 1323 | "config" : { 1324 | "kid" : [ "32662424-89a4-4d1a-a48d-f30b21e903ff" ], 1325 | "secret" : [ "fci1EKUY6L2_eW1dmkLkAlA1TCVIemN2wohv973GsC1Oa1ydAkylTxVKY_45YN8Q2ZKKOOqvNRmQy-_R1B2x3w" ], 1326 | "priority" : [ "100" ], 1327 | "algorithm" : [ "HS256" ] 1328 | } 1329 | }, { 1330 | "id" : "8c44d893-0ea2-4db1-83c3-3d24d023e478", 1331 | "name" : "rsa-generated", 1332 | "providerId" : "rsa-generated", 1333 | "subComponents" : { }, 1334 | "config" : { 1335 | "privateKey" : [ "MIIEowIBAAKCAQEAgPkPUBI4XGapny3yUkItGdllOQXGn+8NO2K3zfB4HPLLg2h8kRTD5jWDkxknVNHc4NSd1/At0cfGwcxuDPnBoXHCldV9w59HJwbDEVdk4u1dcg0VB+YPOt3elFRxMqSbpb2b+7qxKOl6dbOGLf5l6O0WCg8OJokJCmaOetXObaaiDVdclsp9GniMzVLB1vpjOyx3nOVS21RcYzI1nfLweleFXATL7QCy5/5BuFeVIcNLCLxf3wGI+amcFtKvWeHzWiXYusaJIOiJmWcD1coFBV5P0UNYKm1/8BKbYlvUugD+h2sqgznvm45mh1sVh69UISCOwo8aU+Siirxk4Gf8/QIDAQABAoIBAD4MFDLYYScK+OWsrByo25vI+6qgPbtpvTrptjWsT4zVvdT9apg9njVdX2xgOIzU3eeIQlvFn7WB3/wSRouViHMMEKoW6Ic5VHjRBv2Lxuxpd4BMDOcc5gzS+qbvrPnJOVxWSPmlCl/9Wz3O3Wm5LvwNO4IhVhRx7tiDGF6+B662Pjp5GGM8mrt40AzPdMsVeF3ZyT+gXHS+marIDRgKmheRhaBMJFYC7nfjaxAjRMbbidGLTizpKNooG5ZxXz7FGiRpVEBXZYyeC1UhA7YMcusTq6Kz2VNln7evePSIIHz99b4kJP8VrT8Yxn7+SVeN7hLG9prF/R8SOJHw8f520X0CgYEA35PLcwyUM2uZzZD3Vx2zUYjh5XgvZfjSTxlQg/VQJAXbYVOniK1X8NpVnyCW4e1AhQIWGpihQfgVe/auTjPTdoxrxE8RoDM8yYST4BBpgNqbcjdtLwUK4srceQlSaTO5C8pUz4i4cXpFE65XWHq3+myPR7cILb7K41qYCCoGe1sCgYEAk60eeHTwYyEGpnEdEgWXOrjC5KBK8SehAhzxQpVF+8QZj2omQYjByVzEnYeiADe3KcoqkGUU0x85KBYZPfc2WuJe9yLm4udTImg95XYc8iyK7sM1q5eR/YFTZEUinVxyGUXivyWlvl2ESnImeqihmF2wPgKZUNE5Bq3T7kq50IcCgYBIoHEBcX+e6IAwx7uhH/PFM6r16MG05UwkB7wg8YpT+VcXWZ5dhrm/cp1HsMVypKhFzLSzdQtFK7qG504d9zXlF55WSb0XBi3j5F5I9evfwKOoSZr9IC02GOHfq4iKxhOBYfuE4wvPSQGxb/vNsSecgLFWgX11prmvexlR5ZzvawKBgC/Kcbb32Serc3R/3LGNX6CgVGoaucYLVh7R8P3kQw60KrVv28uPj28z92knkLTTUxJSG645GCEu1Jd1d1vHWi7VXXhLMj8yL4ROCeHtdHanFZspT4AlgBhzNuKXQRl95mrpY/UKIPZXW02gXXWKhylBAJ1AyA8Qdo0Dyjcuta2XAoGBALJLiRMe/V2ubQNePuTAXXK7Ikj3Bqs9Qu7JRTaKVRiwbPDmPvq43FRK2sVtJPOq3BS2o+7n+8ZmE0Ywd/wA0d/qcL6pUD5SMfe9UsWMXsju/QAw4vQ4mw4WPkX8x8kI76t1CPM93zu0Ke/iwzr7CikJ1vRIgQowgzrTpYqsfumn" ], 1336 | "certificate" : [ "MIIClzCCAX8CBgF4ovFwVjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARhY21lMB4XDTIxMDQwNTE2NDg1NVoXDTMxMDQwNTE2NTAzNVowDzENMAsGA1UEAwwEYWNtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAID5D1ASOFxmqZ8t8lJCLRnZZTkFxp/vDTtit83weBzyy4NofJEUw+Y1g5MZJ1TR3ODUndfwLdHHxsHMbgz5waFxwpXVfcOfRycGwxFXZOLtXXINFQfmDzrd3pRUcTKkm6W9m/u6sSjpenWzhi3+ZejtFgoPDiaJCQpmjnrVzm2mog1XXJbKfRp4jM1Swdb6Yzssd5zlUttUXGMyNZ3y8HpXhVwEy+0Asuf+QbhXlSHDSwi8X98BiPmpnBbSr1nh81ol2LrGiSDoiZlnA9XKBQVeT9FDWCptf/ASm2Jb1LoA/odrKoM575uOZodbFYevVCEgjsKPGlPkooq8ZOBn/P0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAN+VETiLp8km3WrfVCoXI8ZJ2eARxnoZF3aSfpHx0i+hxRCJzGFM81NKZZKVyqbGsJla/k5NEyJOUpo2iXmnSO2mnDz3g4ACKek2cKHjr3xu56Zw7SZaScKiKQwfc/zTPbfzNIXkClfpxO2/EZRvwiw6e6U03kv0byd+BDknY9SByXgxG2z38CVGl0bUgB9UNZchFwK0OFSL74S/+YEYchao35gThhDHu1Fq5AWpo0YlmOss83iSeFYanMUBH55UETj0dsF0a4sd3piuPqCUDTQNoVGUXnpKAWI9Jvof70aoH1lnTBta9qbNLyTsecgoHHJwZ9WKvF5bmJqBWjIm8MA==" ], 1337 | "priority" : [ "100" ] 1338 | } 1339 | }, { 1340 | "id" : "b173a97d-c779-444a-9d02-c13a1b7f1941", 1341 | "name" : "aes-generated", 1342 | "providerId" : "aes-generated", 1343 | "subComponents" : { }, 1344 | "config" : { 1345 | "kid" : [ "fd1e9b64-973a-401c-bc55-1e67207d2827" ], 1346 | "secret" : [ "fpOb_Zeq0DzUEbms62tfVw" ], 1347 | "priority" : [ "100" ] 1348 | } 1349 | } ] 1350 | }, 1351 | "internationalizationEnabled" : false, 1352 | "supportedLocales" : [ ], 1353 | "authenticationFlows" : [ { 1354 | "id" : "a422f934-8436-42bc-b5dc-029657790dbe", 1355 | "alias" : "Account verification options", 1356 | "description" : "Method with which to verity the existing account", 1357 | "providerId" : "basic-flow", 1358 | "topLevel" : false, 1359 | "builtIn" : true, 1360 | "authenticationExecutions" : [ { 1361 | "authenticator" : "idp-email-verification", 1362 | "requirement" : "ALTERNATIVE", 1363 | "priority" : 10, 1364 | "userSetupAllowed" : false, 1365 | "autheticatorFlow" : false 1366 | }, { 1367 | "requirement" : "ALTERNATIVE", 1368 | "priority" : 20, 1369 | "flowAlias" : "Verify Existing Account by Re-authentication", 1370 | "userSetupAllowed" : false, 1371 | "autheticatorFlow" : true 1372 | } ] 1373 | }, { 1374 | "id" : "c7618c56-0bf4-4c30-9827-937747f697f3", 1375 | "alias" : "Authentication Options", 1376 | "description" : "Authentication options.", 1377 | "providerId" : "basic-flow", 1378 | "topLevel" : false, 1379 | "builtIn" : true, 1380 | "authenticationExecutions" : [ { 1381 | "authenticator" : "basic-auth", 1382 | "requirement" : "REQUIRED", 1383 | "priority" : 10, 1384 | "userSetupAllowed" : false, 1385 | "autheticatorFlow" : false 1386 | }, { 1387 | "authenticator" : "basic-auth-otp", 1388 | "requirement" : "DISABLED", 1389 | "priority" : 20, 1390 | "userSetupAllowed" : false, 1391 | "autheticatorFlow" : false 1392 | }, { 1393 | "authenticator" : "auth-spnego", 1394 | "requirement" : "DISABLED", 1395 | "priority" : 30, 1396 | "userSetupAllowed" : false, 1397 | "autheticatorFlow" : false 1398 | } ] 1399 | }, { 1400 | "id" : "86c2b8f6-39c0-4500-ae31-9056be930884", 1401 | "alias" : "Browser - Conditional OTP", 1402 | "description" : "Flow to determine if the OTP is required for the authentication", 1403 | "providerId" : "basic-flow", 1404 | "topLevel" : false, 1405 | "builtIn" : true, 1406 | "authenticationExecutions" : [ { 1407 | "authenticator" : "conditional-user-configured", 1408 | "requirement" : "REQUIRED", 1409 | "priority" : 10, 1410 | "userSetupAllowed" : false, 1411 | "autheticatorFlow" : false 1412 | }, { 1413 | "authenticator" : "auth-otp-form", 1414 | "requirement" : "REQUIRED", 1415 | "priority" : 20, 1416 | "userSetupAllowed" : false, 1417 | "autheticatorFlow" : false 1418 | } ] 1419 | }, { 1420 | "id" : "a62322c3-9c76-412c-b154-2d91ec5b15dd", 1421 | "alias" : "Direct Grant - Conditional OTP", 1422 | "description" : "Flow to determine if the OTP is required for the authentication", 1423 | "providerId" : "basic-flow", 1424 | "topLevel" : false, 1425 | "builtIn" : true, 1426 | "authenticationExecutions" : [ { 1427 | "authenticator" : "conditional-user-configured", 1428 | "requirement" : "REQUIRED", 1429 | "priority" : 10, 1430 | "userSetupAllowed" : false, 1431 | "autheticatorFlow" : false 1432 | }, { 1433 | "authenticator" : "direct-grant-validate-otp", 1434 | "requirement" : "REQUIRED", 1435 | "priority" : 20, 1436 | "userSetupAllowed" : false, 1437 | "autheticatorFlow" : false 1438 | } ] 1439 | }, { 1440 | "id" : "ae76de99-88bc-4ed0-919d-c6659419f3c0", 1441 | "alias" : "First broker login - Conditional OTP", 1442 | "description" : "Flow to determine if the OTP is required for the authentication", 1443 | "providerId" : "basic-flow", 1444 | "topLevel" : false, 1445 | "builtIn" : true, 1446 | "authenticationExecutions" : [ { 1447 | "authenticator" : "conditional-user-configured", 1448 | "requirement" : "REQUIRED", 1449 | "priority" : 10, 1450 | "userSetupAllowed" : false, 1451 | "autheticatorFlow" : false 1452 | }, { 1453 | "authenticator" : "auth-otp-form", 1454 | "requirement" : "REQUIRED", 1455 | "priority" : 20, 1456 | "userSetupAllowed" : false, 1457 | "autheticatorFlow" : false 1458 | } ] 1459 | }, { 1460 | "id" : "0742a721-05fd-4b35-8e5e-5037897f19c9", 1461 | "alias" : "Handle Existing Account", 1462 | "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", 1463 | "providerId" : "basic-flow", 1464 | "topLevel" : false, 1465 | "builtIn" : true, 1466 | "authenticationExecutions" : [ { 1467 | "authenticator" : "idp-confirm-link", 1468 | "requirement" : "REQUIRED", 1469 | "priority" : 10, 1470 | "userSetupAllowed" : false, 1471 | "autheticatorFlow" : false 1472 | }, { 1473 | "requirement" : "REQUIRED", 1474 | "priority" : 20, 1475 | "flowAlias" : "Account verification options", 1476 | "userSetupAllowed" : false, 1477 | "autheticatorFlow" : true 1478 | } ] 1479 | }, { 1480 | "id" : "d63317fe-7b59-4157-b868-c3eaf27b180a", 1481 | "alias" : "Reset - Conditional OTP", 1482 | "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", 1483 | "providerId" : "basic-flow", 1484 | "topLevel" : false, 1485 | "builtIn" : true, 1486 | "authenticationExecutions" : [ { 1487 | "authenticator" : "conditional-user-configured", 1488 | "requirement" : "REQUIRED", 1489 | "priority" : 10, 1490 | "userSetupAllowed" : false, 1491 | "autheticatorFlow" : false 1492 | }, { 1493 | "authenticator" : "reset-otp", 1494 | "requirement" : "REQUIRED", 1495 | "priority" : 20, 1496 | "userSetupAllowed" : false, 1497 | "autheticatorFlow" : false 1498 | } ] 1499 | }, { 1500 | "id" : "f1fadbc9-92bb-4d15-8e90-41f7bcae30a8", 1501 | "alias" : "User creation or linking", 1502 | "description" : "Flow for the existing/non-existing user alternatives", 1503 | "providerId" : "basic-flow", 1504 | "topLevel" : false, 1505 | "builtIn" : true, 1506 | "authenticationExecutions" : [ { 1507 | "authenticatorConfig" : "create unique user config", 1508 | "authenticator" : "idp-create-user-if-unique", 1509 | "requirement" : "ALTERNATIVE", 1510 | "priority" : 10, 1511 | "userSetupAllowed" : false, 1512 | "autheticatorFlow" : false 1513 | }, { 1514 | "requirement" : "ALTERNATIVE", 1515 | "priority" : 20, 1516 | "flowAlias" : "Handle Existing Account", 1517 | "userSetupAllowed" : false, 1518 | "autheticatorFlow" : true 1519 | } ] 1520 | }, { 1521 | "id" : "b5c4686c-22c6-4beb-92bb-ab7c312ea1f0", 1522 | "alias" : "Verify Existing Account by Re-authentication", 1523 | "description" : "Reauthentication of existing account", 1524 | "providerId" : "basic-flow", 1525 | "topLevel" : false, 1526 | "builtIn" : true, 1527 | "authenticationExecutions" : [ { 1528 | "authenticator" : "idp-username-password-form", 1529 | "requirement" : "REQUIRED", 1530 | "priority" : 10, 1531 | "userSetupAllowed" : false, 1532 | "autheticatorFlow" : false 1533 | }, { 1534 | "requirement" : "CONDITIONAL", 1535 | "priority" : 20, 1536 | "flowAlias" : "First broker login - Conditional OTP", 1537 | "userSetupAllowed" : false, 1538 | "autheticatorFlow" : true 1539 | } ] 1540 | }, { 1541 | "id" : "d2701f9c-b177-4996-939d-83436dda5b02", 1542 | "alias" : "browser", 1543 | "description" : "browser based authentication", 1544 | "providerId" : "basic-flow", 1545 | "topLevel" : true, 1546 | "builtIn" : true, 1547 | "authenticationExecutions" : [ { 1548 | "authenticator" : "auth-cookie", 1549 | "requirement" : "ALTERNATIVE", 1550 | "priority" : 10, 1551 | "userSetupAllowed" : false, 1552 | "autheticatorFlow" : false 1553 | }, { 1554 | "authenticator" : "auth-spnego", 1555 | "requirement" : "DISABLED", 1556 | "priority" : 20, 1557 | "userSetupAllowed" : false, 1558 | "autheticatorFlow" : false 1559 | }, { 1560 | "authenticator" : "identity-provider-redirector", 1561 | "requirement" : "ALTERNATIVE", 1562 | "priority" : 25, 1563 | "userSetupAllowed" : false, 1564 | "autheticatorFlow" : false 1565 | }, { 1566 | "requirement" : "ALTERNATIVE", 1567 | "priority" : 30, 1568 | "flowAlias" : "forms", 1569 | "userSetupAllowed" : false, 1570 | "autheticatorFlow" : true 1571 | } ] 1572 | }, { 1573 | "id" : "e0bbebbf-cf5f-4c67-947c-3da17958297a", 1574 | "alias" : "clients", 1575 | "description" : "Base authentication for clients", 1576 | "providerId" : "client-flow", 1577 | "topLevel" : true, 1578 | "builtIn" : true, 1579 | "authenticationExecutions" : [ { 1580 | "authenticator" : "client-secret", 1581 | "requirement" : "ALTERNATIVE", 1582 | "priority" : 10, 1583 | "userSetupAllowed" : false, 1584 | "autheticatorFlow" : false 1585 | }, { 1586 | "authenticator" : "client-jwt", 1587 | "requirement" : "ALTERNATIVE", 1588 | "priority" : 20, 1589 | "userSetupAllowed" : false, 1590 | "autheticatorFlow" : false 1591 | }, { 1592 | "authenticator" : "client-secret-jwt", 1593 | "requirement" : "ALTERNATIVE", 1594 | "priority" : 30, 1595 | "userSetupAllowed" : false, 1596 | "autheticatorFlow" : false 1597 | }, { 1598 | "authenticator" : "client-x509", 1599 | "requirement" : "ALTERNATIVE", 1600 | "priority" : 40, 1601 | "userSetupAllowed" : false, 1602 | "autheticatorFlow" : false 1603 | } ] 1604 | }, { 1605 | "id" : "cb7810e3-f13c-48c6-bc7e-438df1b1cb9c", 1606 | "alias" : "direct grant", 1607 | "description" : "OpenID Connect Resource Owner Grant", 1608 | "providerId" : "basic-flow", 1609 | "topLevel" : true, 1610 | "builtIn" : true, 1611 | "authenticationExecutions" : [ { 1612 | "authenticator" : "direct-grant-validate-username", 1613 | "requirement" : "REQUIRED", 1614 | "priority" : 10, 1615 | "userSetupAllowed" : false, 1616 | "autheticatorFlow" : false 1617 | }, { 1618 | "authenticator" : "direct-grant-validate-password", 1619 | "requirement" : "REQUIRED", 1620 | "priority" : 20, 1621 | "userSetupAllowed" : false, 1622 | "autheticatorFlow" : false 1623 | }, { 1624 | "requirement" : "CONDITIONAL", 1625 | "priority" : 30, 1626 | "flowAlias" : "Direct Grant - Conditional OTP", 1627 | "userSetupAllowed" : false, 1628 | "autheticatorFlow" : true 1629 | } ] 1630 | }, { 1631 | "id" : "cc8f25de-10d6-4cdc-ba4d-95a6e5e25525", 1632 | "alias" : "docker auth", 1633 | "description" : "Used by Docker clients to authenticate against the IDP", 1634 | "providerId" : "basic-flow", 1635 | "topLevel" : true, 1636 | "builtIn" : true, 1637 | "authenticationExecutions" : [ { 1638 | "authenticator" : "docker-http-basic-authenticator", 1639 | "requirement" : "REQUIRED", 1640 | "priority" : 10, 1641 | "userSetupAllowed" : false, 1642 | "autheticatorFlow" : false 1643 | } ] 1644 | }, { 1645 | "id" : "0a0408f6-e4d8-4ff5-b99c-504b7a23cb13", 1646 | "alias" : "first broker login", 1647 | "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", 1648 | "providerId" : "basic-flow", 1649 | "topLevel" : true, 1650 | "builtIn" : true, 1651 | "authenticationExecutions" : [ { 1652 | "authenticatorConfig" : "review profile config", 1653 | "authenticator" : "idp-review-profile", 1654 | "requirement" : "REQUIRED", 1655 | "priority" : 10, 1656 | "userSetupAllowed" : false, 1657 | "autheticatorFlow" : false 1658 | }, { 1659 | "requirement" : "REQUIRED", 1660 | "priority" : 20, 1661 | "flowAlias" : "User creation or linking", 1662 | "userSetupAllowed" : false, 1663 | "autheticatorFlow" : true 1664 | } ] 1665 | }, { 1666 | "id" : "40e56cec-70f2-412b-865a-826de0ef2f61", 1667 | "alias" : "forms", 1668 | "description" : "Username, password, otp and other auth forms.", 1669 | "providerId" : "basic-flow", 1670 | "topLevel" : false, 1671 | "builtIn" : true, 1672 | "authenticationExecutions" : [ { 1673 | "authenticator" : "auth-username-password-form", 1674 | "requirement" : "REQUIRED", 1675 | "priority" : 10, 1676 | "userSetupAllowed" : false, 1677 | "autheticatorFlow" : false 1678 | }, { 1679 | "requirement" : "CONDITIONAL", 1680 | "priority" : 20, 1681 | "flowAlias" : "Browser - Conditional OTP", 1682 | "userSetupAllowed" : false, 1683 | "autheticatorFlow" : true 1684 | } ] 1685 | }, { 1686 | "id" : "c416db40-dc84-4f58-8d52-4c9a6afe4867", 1687 | "alias" : "http challenge", 1688 | "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", 1689 | "providerId" : "basic-flow", 1690 | "topLevel" : true, 1691 | "builtIn" : true, 1692 | "authenticationExecutions" : [ { 1693 | "authenticator" : "no-cookie-redirect", 1694 | "requirement" : "REQUIRED", 1695 | "priority" : 10, 1696 | "userSetupAllowed" : false, 1697 | "autheticatorFlow" : false 1698 | }, { 1699 | "requirement" : "REQUIRED", 1700 | "priority" : 20, 1701 | "flowAlias" : "Authentication Options", 1702 | "userSetupAllowed" : false, 1703 | "autheticatorFlow" : true 1704 | } ] 1705 | }, { 1706 | "id" : "d2f561df-ebf6-47cf-8fe0-1ab85cabf2cd", 1707 | "alias" : "registration", 1708 | "description" : "registration flow", 1709 | "providerId" : "basic-flow", 1710 | "topLevel" : true, 1711 | "builtIn" : true, 1712 | "authenticationExecutions" : [ { 1713 | "authenticator" : "registration-page-form", 1714 | "requirement" : "REQUIRED", 1715 | "priority" : 10, 1716 | "flowAlias" : "registration form", 1717 | "userSetupAllowed" : false, 1718 | "autheticatorFlow" : true 1719 | } ] 1720 | }, { 1721 | "id" : "f667e507-0b01-44ee-878c-c478164f7c33", 1722 | "alias" : "registration form", 1723 | "description" : "registration form", 1724 | "providerId" : "form-flow", 1725 | "topLevel" : false, 1726 | "builtIn" : true, 1727 | "authenticationExecutions" : [ { 1728 | "authenticator" : "registration-user-creation", 1729 | "requirement" : "REQUIRED", 1730 | "priority" : 20, 1731 | "userSetupAllowed" : false, 1732 | "autheticatorFlow" : false 1733 | }, { 1734 | "authenticator" : "registration-profile-action", 1735 | "requirement" : "REQUIRED", 1736 | "priority" : 40, 1737 | "userSetupAllowed" : false, 1738 | "autheticatorFlow" : false 1739 | }, { 1740 | "authenticator" : "registration-password-action", 1741 | "requirement" : "REQUIRED", 1742 | "priority" : 50, 1743 | "userSetupAllowed" : false, 1744 | "autheticatorFlow" : false 1745 | }, { 1746 | "authenticator" : "registration-recaptcha-action", 1747 | "requirement" : "DISABLED", 1748 | "priority" : 60, 1749 | "userSetupAllowed" : false, 1750 | "autheticatorFlow" : false 1751 | } ] 1752 | }, { 1753 | "id" : "9b4a8f0b-8ace-47f3-89ef-c0c259b219bc", 1754 | "alias" : "reset credentials", 1755 | "description" : "Reset credentials for a user if they forgot their password or something", 1756 | "providerId" : "basic-flow", 1757 | "topLevel" : true, 1758 | "builtIn" : true, 1759 | "authenticationExecutions" : [ { 1760 | "authenticator" : "reset-credentials-choose-user", 1761 | "requirement" : "REQUIRED", 1762 | "priority" : 10, 1763 | "userSetupAllowed" : false, 1764 | "autheticatorFlow" : false 1765 | }, { 1766 | "authenticator" : "reset-credential-email", 1767 | "requirement" : "REQUIRED", 1768 | "priority" : 20, 1769 | "userSetupAllowed" : false, 1770 | "autheticatorFlow" : false 1771 | }, { 1772 | "authenticator" : "reset-password", 1773 | "requirement" : "REQUIRED", 1774 | "priority" : 30, 1775 | "userSetupAllowed" : false, 1776 | "autheticatorFlow" : false 1777 | }, { 1778 | "requirement" : "CONDITIONAL", 1779 | "priority" : 40, 1780 | "flowAlias" : "Reset - Conditional OTP", 1781 | "userSetupAllowed" : false, 1782 | "autheticatorFlow" : true 1783 | } ] 1784 | }, { 1785 | "id" : "186bed01-f1e4-48c2-a4bd-1b28e32b99be", 1786 | "alias" : "saml ecp", 1787 | "description" : "SAML ECP Profile Authentication Flow", 1788 | "providerId" : "basic-flow", 1789 | "topLevel" : true, 1790 | "builtIn" : true, 1791 | "authenticationExecutions" : [ { 1792 | "authenticator" : "http-basic-authenticator", 1793 | "requirement" : "REQUIRED", 1794 | "priority" : 10, 1795 | "userSetupAllowed" : false, 1796 | "autheticatorFlow" : false 1797 | } ] 1798 | } ], 1799 | "authenticatorConfig" : [ { 1800 | "id" : "2a43b55e-be98-4089-8d7b-6cee7770ffe0", 1801 | "alias" : "create unique user config", 1802 | "config" : { 1803 | "require.password.update.after.registration" : "false" 1804 | } 1805 | }, { 1806 | "id" : "2eba2ed1-7a21-4720-a562-9e08141217eb", 1807 | "alias" : "review profile config", 1808 | "config" : { 1809 | "update.profile.on.first.login" : "missing" 1810 | } 1811 | } ], 1812 | "requiredActions" : [ { 1813 | "alias" : "CONFIGURE_TOTP", 1814 | "name" : "Configure OTP", 1815 | "providerId" : "CONFIGURE_TOTP", 1816 | "enabled" : true, 1817 | "defaultAction" : false, 1818 | "priority" : 10, 1819 | "config" : { } 1820 | }, { 1821 | "alias" : "terms_and_conditions", 1822 | "name" : "Terms and Conditions", 1823 | "providerId" : "terms_and_conditions", 1824 | "enabled" : false, 1825 | "defaultAction" : false, 1826 | "priority" : 20, 1827 | "config" : { } 1828 | }, { 1829 | "alias" : "UPDATE_PASSWORD", 1830 | "name" : "Update Password", 1831 | "providerId" : "UPDATE_PASSWORD", 1832 | "enabled" : true, 1833 | "defaultAction" : false, 1834 | "priority" : 30, 1835 | "config" : { } 1836 | }, { 1837 | "alias" : "UPDATE_PROFILE", 1838 | "name" : "Update Profile", 1839 | "providerId" : "UPDATE_PROFILE", 1840 | "enabled" : true, 1841 | "defaultAction" : false, 1842 | "priority" : 40, 1843 | "config" : { } 1844 | }, { 1845 | "alias" : "VERIFY_EMAIL", 1846 | "name" : "Verify Email", 1847 | "providerId" : "VERIFY_EMAIL", 1848 | "enabled" : true, 1849 | "defaultAction" : false, 1850 | "priority" : 50, 1851 | "config" : { } 1852 | }, { 1853 | "alias" : "delete_account", 1854 | "name" : "Delete Account", 1855 | "providerId" : "delete_account", 1856 | "enabled" : false, 1857 | "defaultAction" : false, 1858 | "priority" : 60, 1859 | "config" : { } 1860 | }, { 1861 | "alias" : "update_user_locale", 1862 | "name" : "Update User Locale", 1863 | "providerId" : "update_user_locale", 1864 | "enabled" : true, 1865 | "defaultAction" : false, 1866 | "priority" : 1000, 1867 | "config" : { } 1868 | } ], 1869 | "browserFlow" : "browser", 1870 | "registrationFlow" : "registration", 1871 | "directGrantFlow" : "direct grant", 1872 | "resetCredentialsFlow" : "reset credentials", 1873 | "clientAuthenticationFlow" : "clients", 1874 | "dockerAuthenticationFlow" : "docker auth", 1875 | "attributes" : { 1876 | "clientOfflineSessionMaxLifespan" : "0", 1877 | "clientSessionIdleTimeout" : "0", 1878 | "clientSessionMaxLifespan" : "0", 1879 | "clientOfflineSessionIdleTimeout" : "0" 1880 | }, 1881 | "keycloakVersion" : "12.0.4", 1882 | "userManagedAccessAllowed" : false 1883 | } --------------------------------------------------------------------------------