├── .github └── workflows │ ├── maven-build.yml │ └── maven-publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── doc ├── screen_01.png ├── screen_02.png ├── screen_03.png ├── screen_04.png ├── screen_05.png ├── screen_06.png ├── screen_07.png ├── screen_08.png ├── screen_09.png └── screen_10.png ├── docker-compose.yml ├── pom.xml ├── settings.xml └── src └── main ├── java └── foundation │ └── softwaredesign │ └── keycloak │ └── authenticators │ ├── CustomAttributeIdpLinkingAuthenticator.java │ └── CustomAttributeIdpLinkingAuthenticatorFactory.java └── resources └── META-INF └── services └── org.keycloak.authentication.AuthenticatorFactory /.github/workflows/maven-build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | packages: write 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up JDK 17 19 | uses: actions/setup-java@v2 20 | with: 21 | java-version: '17' 22 | distribution: 'adopt' 23 | server-id: github 24 | settings-path: ${{ github.workspace }} 25 | 26 | - name: Build with Maven 27 | run: mvn -ntp -B verify --file pom.xml 28 | 29 | - name: Publish to GitHub Packages Apache Maven 30 | run: mvn clean install -s $GITHUB_WORKSPACE/settings.xml 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/maven-publish.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+.[0-9]+' 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | packages: write 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up JDK 17 19 | uses: actions/setup-java@v2 20 | with: 21 | java-version: '17' 22 | distribution: 'adopt' 23 | server-id: github 24 | settings-path: ${{ github.workspace }} 25 | 26 | - name: Build with Maven 27 | run: mvn -ntp -B package --file pom.xml 28 | 29 | - name: Publish to GitHub Packages Apache Maven 30 | run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | pom.xml.versionsBackup 4 | target/ 5 | .env 6 | keycloak/realm-export.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Lucas Reeh 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keycloak Custom Attribute IDP Linking 2 | 3 | ![Build](https://github.com/sd-f/keycloak-custom-attribute-idp-linking/actions/workflows/maven-build.yml/badge.svg) 4 | ![Release](https://github.com/sd-f/keycloak-custom-attribute-idp-linking/actions/workflows/maven-publish.yml/badge.svg) 5 | 6 | Keycloak default authenticator flows for external identity provider brokering only match existing users only on username and 7 | password attributes. If you want to lookup user with different attributes you can use this extension. If you for example 8 | get attribute `eid` from your external provider and want your local user, for example from ldap storage provider matched where 9 | you store this `eid` value in an attribute with name `u_eid` you can do so. Matching attributes will add identity provider 10 | links in keycloak and your users will not get created twice in your keycloak database. An example would be European Union 11 | [eIDAS](https://digital-strategy.ec.europa.eu/en/policies/discover-eidas) project with services/members like 12 | [ID Austria](https://www.oesterreich.gv.at/id-austria.html). 13 | 14 | ## Development 15 | 16 | ```shell 17 | mvn clean install 18 | ``` 19 | 20 | ```shell 21 | docker-compose up 22 | ``` 23 | 24 | Update Plugin in container by running ```mvn install```. 25 | 26 | Attach remote jvm debug session on port 5005 (default). 27 | 28 | ## Installation 29 | 30 | Tested on Keycloak `15.0.2`, `17.0.0`, `22.0.3. 31 | 32 | ### Keycloak >= v17.0.0 33 | 34 | After Packaging the project with, 35 | 36 | ```sh 37 | mvn package -f "./pom.xml" 38 | ``` 39 | 40 | deploy the `keycloak-custom-attribute-idp-linking-2.0.1.jar` to `/opt/keycloak/providers` and rebuild keycloak to bring this provider in. 41 | 42 | #### Deploy custom attribute provider 43 | 44 | ```sh 45 | # Sometimes (depending on versions), this dir is not present; 46 | [ ! -d "/opt/keycloak/providers" ] && sudo mkdir /opt/keycloak/providers; 47 | sudo mv keycloak-custom-attribute-idp-linking-2.0.1.jar /opt/keycloak/providers/keycloak-custom-attribute-idp-linking-2.0.1.jar; 48 | ``` 49 | 50 | #### Rebuild and Restart Keycloak 51 | 52 | **all-in-one:** 53 | *This is the suggested method, check [Keycloak's Docs](https://keycloak.org/) for more configuration options from the cli* 54 | 55 | ```sh 56 | # This will rebuild keycloak and make the provider available in the Keycloak admin console 57 | sudo /opt/keycloak/bin/kc.sh start --auto-build; 58 | ``` 59 | 60 | **build only:** 61 | 62 | ```shell 63 | /opt/keycloak/bin/kc.sh build 64 | ``` 65 | 66 | ### Keycloak <= 15.0.2 67 | 68 | Copy or mount plugin in your keycloak installation depending on your environment (k8s, compose, gke). 69 | For example in `/opt/jboss/keycloak/standalone/deployments/` (see file docker-compose.yml). You should see something like 70 | following in your keycloak log: 71 | 72 | ```shell 73 | ... 74 | WFLYSRV0010: Deployed "keycloak-custom-attribute-idp-linking-1.0.0.jar" (runtime-name : "keycloak-custom-attribute-idp-linking-1.0.0.jar") 75 | ... 76 | ``` 77 | 78 | Now you can use `Custom Attribute IDP Linking` Authenticator in your Keycloak Authentication configuration. 79 | 80 | ## Using the Provider 81 | 82 | ![Custom Attribute IDP Linking](doc/screen_02.png) 83 | 84 | Setup below is only for testing and your production configuration might differ. 85 | Read more about [Keycloak Authenticators and Flows Configurations](https://www.keycloak.org/docs/latest/server_admin/). 86 | 87 | ### [Optional] Check your external provider attribute mapping 88 | 89 | If necessary check whether you really map and import the attribute you want to use for matching users. 90 | 91 | ![IDP attribute mappers](doc/screen_03.png) 92 | 93 | ![IDP custom attribute mapping](doc/screen_01.png) 94 | 95 | ### Create Custom Authentication Flow 96 | 97 | Go to Authentication and create a new Flow. In this example will call it **Auto-linking**. Next add this extensions 98 | **Custom Attribute IDP Linking** execution as well as the standard **Automatically Set Existing User** as a fallback. 99 | 100 | ![Custom authentication flow](doc/screen_04.png) 101 | 102 | Adjust configuration to your needs. Attribute name on external side and lookup attribute for existing users. 103 | 104 | ![Set custom execution config](doc/screen_05.png) 105 | 106 | ![Adjust custom execution config](doc/screen_06.png) 107 | 108 | ### Set first login flow 109 | 110 | Set first login flow in your identity provider configuration to your newly created custom flow. 111 | 112 | ![Set first login flow](doc/screen_07.png) 113 | 114 | ### [Optional ;)] Check config 115 | 116 | Login in using your external provider and check if user get linked to the provider. 117 | 118 | ![IDP Login](doc/screen_08.png) 119 | 120 | ![User id provider links](doc/screen_09.png) 121 | 122 | ![User id provider link](doc/screen_10.png) 123 | -------------------------------------------------------------------------------- /doc/screen_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_01.png -------------------------------------------------------------------------------- /doc/screen_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_02.png -------------------------------------------------------------------------------- /doc/screen_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_03.png -------------------------------------------------------------------------------- /doc/screen_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_04.png -------------------------------------------------------------------------------- /doc/screen_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_05.png -------------------------------------------------------------------------------- /doc/screen_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_06.png -------------------------------------------------------------------------------- /doc/screen_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_07.png -------------------------------------------------------------------------------- /doc/screen_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_08.png -------------------------------------------------------------------------------- /doc/screen_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_09.png -------------------------------------------------------------------------------- /doc/screen_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sd-f/keycloak-custom-attribute-idp-linking/6462fddb00b15215f9b8c2036f0a594848acc311/doc/screen_10.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | keycloak: 5 | image: quay.io/keycloak/keycloak:22.0.3 6 | container_name: keycloak 7 | environment: 8 | KC_HEALTH_ENABLED: "true" 9 | KC_METRICS_ENABLED: "true" 10 | KC_HTTP_ENABLED: "true" 11 | KEYCLOAK_ADMIN: "admin" 12 | KEYCLOAK_ADMIN_PASSWORD: "admin" 13 | DEBUG: "true" 14 | DEBUG_PORT: "*:5005" 15 | command: ["start-dev", "--auto-build"] 16 | ports: 17 | - 8081:8080 18 | - 5005:5005 19 | volumes: 20 | - ./target/keycloak-custom-attribute-idp-linking-2.1.1-SNAPSHOT.jar:/opt/keycloak/providers/keycloak-custom-attribute-idp-linking-2.1.1-SNAPSHOT.jar 21 | # if you want to import a realm or some other things 22 | # - ./import/something.json:/opt/keycloak/data/import/something.json -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | foundation.softwaredesign 7 | 2.1.1-SNAPSHOT 8 | 9 | keycloak-custom-attribute-idp-linking 10 | keycloak-custom-attribute-idp-linking 11 | Keycloak plugin to link users from external IDP to exiting users based on custom attribute 12 | 13 | 14 | 15 | Apache License, Version 2.0 16 | http://www.apache.org/licenses/LICENSE-2.0.txt 17 | repo 18 | 19 | 20 | 21 | 22 | https://github.com/sd-f/keycloak-custom-attribute-idp-linking 23 | 24 | 25 | 26 | 27 | luke 28 | Lucas Reeh 29 | lr86gm@gmail.com 30 | 31 | 32 | 33 | 34 | UTF-8 35 | UTF-8 36 | 17 37 | 17 38 | 22.0.3 39 | 40 | 41 | 42 | 43 | org.keycloak 44 | keycloak-server-spi 45 | ${keycloak.version} 46 | provided 47 | 48 | 49 | org.keycloak 50 | keycloak-model-jpa 51 | ${keycloak.version} 52 | provided 53 | 54 | 55 | org.keycloak 56 | keycloak-ldap-federation 57 | ${keycloak.version} 58 | provided 59 | 60 | 61 | org.keycloak 62 | keycloak-services 63 | ${keycloak.version} 64 | 65 | 66 | org.jboss.logging 67 | jboss-logging 68 | 3.5.0.Final 69 | provided 70 | 71 | 72 | org.keycloak 73 | keycloak-core 74 | ${keycloak.version} 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-jar-plugin 84 | 3.3.0 85 | 86 | 87 | 88 | org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | github 99 | GitHub sd-f Apache Maven Packages 100 | https://maven.pkg.github.com/sd-f/keycloak-custom-attribute-idp-linking 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | github 8 | 9 | 10 | 11 | 12 | github 13 | 14 | 15 | central 16 | https://repo1.maven.org/maven2 17 | 18 | 19 | github 20 | https://maven.pkg.github.com/sd-f/keycloak-custom-attribute-idp-linking 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | github 32 | sd-f 33 | $GITHUB_TOKEN 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/foundation/softwaredesign/keycloak/authenticators/CustomAttributeIdpLinkingAuthenticator.java: -------------------------------------------------------------------------------- 1 | package foundation.softwaredesign.keycloak.authenticators; 2 | 3 | import jakarta.ws.rs.core.Response; 4 | import org.jboss.logging.Logger; 5 | import org.keycloak.authentication.AuthenticationFlowContext; 6 | import org.keycloak.authentication.AuthenticationFlowError; 7 | import org.keycloak.authentication.authenticators.broker.IdpAutoLinkAuthenticator; 8 | import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext; 9 | import org.keycloak.broker.provider.BrokeredIdentityContext; 10 | import org.keycloak.events.Errors; 11 | import org.keycloak.models.AuthenticatorConfigModel; 12 | import org.keycloak.models.UserModel; 13 | import org.keycloak.services.messages.Messages; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Optional; 18 | 19 | public class CustomAttributeIdpLinkingAuthenticator extends IdpAutoLinkAuthenticator { 20 | 21 | private static final Logger log = Logger.getLogger(CustomAttributeIdpLinkingAuthenticator.class); 22 | 23 | @Override 24 | protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, 25 | BrokeredIdentityContext brokerContext) { 26 | 27 | AuthenticatorConfigModel config = validateConfig(context); 28 | String failOnNoMatch = config.getConfig() 29 | .get(CustomAttributeIdpLinkingAuthenticatorFactory.CONFIG_FAIL_ON_NO_MATCH_ATTRIBUTE); 30 | UserModel existingUser = findMatchingUser(context, brokerContext, config); 31 | 32 | if (existingUser != null) { 33 | log.debugf( 34 | "User '%s' is set to authentication context when link with identity provider '%s' . Identity provider username is '%s' ", 35 | existingUser.getUsername(), 36 | brokerContext.getIdpConfig().getAlias(), brokerContext.getUsername()); 37 | 38 | context.setUser(existingUser); 39 | context.success(); 40 | } else { 41 | if (failOnNoMatch.equals("true")) { 42 | sendFailureChallenge(context, Response.Status.BAD_REQUEST, Errors.USER_NOT_FOUND, 43 | Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, AuthenticationFlowError.ACCESS_DENIED); 44 | } 45 | context.attempted(); 46 | } 47 | 48 | } 49 | 50 | private AuthenticatorConfigModel validateConfig(AuthenticationFlowContext context) { 51 | if (context.getAuthenticatorConfig() == null) { 52 | log.warn("Config must not be empty."); 53 | return null; 54 | } 55 | if (context.getAuthenticatorConfig().getConfig() == null) { 56 | log.warn("Config must not be empty."); 57 | return null; 58 | } 59 | return context.getAuthenticatorConfig(); 60 | } 61 | 62 | protected UserModel findMatchingUser(AuthenticationFlowContext context, BrokeredIdentityContext brokerContext, 63 | AuthenticatorConfigModel config) { 64 | 65 | String idpAttribute = config.getConfig() 66 | .get(CustomAttributeIdpLinkingAuthenticatorFactory.CONFIG_IDP_ATTRIBUTE); 67 | 68 | if (idpAttribute == null) { 69 | log.warn("Identiy provider attribute must not be null."); 70 | return null; 71 | } 72 | 73 | if (idpAttribute.isEmpty()) { 74 | log.warn("Identiy provider attribute must not be empty."); 75 | return null; 76 | } 77 | 78 | String attribute = config.getConfig() 79 | .get(CustomAttributeIdpLinkingAuthenticatorFactory.CONFIG_LOOKUP_ATTRIBUTE); 80 | 81 | if (attribute == null) { 82 | log.warn("Lookup Attribute must not be null."); 83 | return null; 84 | } 85 | 86 | if (attribute.isEmpty()) { 87 | log.warn("Lookup Attribute must not be empty."); 88 | return null; 89 | } 90 | 91 | Object o = brokerContext.getContextData().get(idpAttribute); 92 | String value = null; 93 | if (o != null) { 94 | if (o instanceof ArrayList) { 95 | List list = (ArrayList) o; 96 | if (!list.isEmpty()) { 97 | if (list.get(0) instanceof String) { 98 | value = (String) list.get(0); 99 | } 100 | } 101 | } else if (o instanceof String) { 102 | value = (String) o; 103 | } else { 104 | log.warn("Unknown type of user attribute value."); 105 | } 106 | } 107 | log.debug("Identity provider attribute \"" + idpAttribute + "\": " + value); 108 | if (value != null) { 109 | Optional user = context.getSession().users() 110 | .searchForUserByUserAttributeStream(context.getRealm(), attribute, value) 111 | .findFirst(); 112 | 113 | if (user.isPresent()) { 114 | return user.get(); 115 | } else { 116 | log.debug("No user found with \"" + attribute + "\" == " + value); 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | @Override 123 | protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, 124 | BrokeredIdentityContext brokerContext) { 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/foundation/softwaredesign/keycloak/authenticators/CustomAttributeIdpLinkingAuthenticatorFactory.java: -------------------------------------------------------------------------------- 1 | package foundation.softwaredesign.keycloak.authenticators; 2 | 3 | import org.jboss.logging.Logger; 4 | import org.keycloak.Config; 5 | import org.keycloak.authentication.Authenticator; 6 | import org.keycloak.authentication.AuthenticatorFactory; 7 | import org.keycloak.authentication.ConfigurableAuthenticatorFactory; 8 | import org.keycloak.models.AuthenticationExecutionModel; 9 | import org.keycloak.models.KeycloakSession; 10 | import org.keycloak.models.KeycloakSessionFactory; 11 | import org.keycloak.provider.ProviderConfigProperty; 12 | import org.keycloak.provider.ProviderConfigurationBuilder; 13 | 14 | import java.util.List; 15 | 16 | public class CustomAttributeIdpLinkingAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory { 17 | 18 | public static final String PROVIDER_ID = "custom-attribute-idp-linking"; 19 | public static final String CONFIG_IDP_ATTRIBUTE = "caila-idp-attribute"; 20 | public static final String CONFIG_LOOKUP_ATTRIBUTE = "caila-lookup-attribute"; 21 | public static final String CONFIG_FAIL_ON_NO_MATCH_ATTRIBUTE = "caila-fail-on-no-match"; 22 | 23 | private static final Logger log = Logger.getLogger(CustomAttributeIdpLinkingAuthenticatorFactory.class); 24 | 25 | static CustomAttributeIdpLinkingAuthenticator SINGLETON = new CustomAttributeIdpLinkingAuthenticator(); 26 | 27 | @Override 28 | public Authenticator create(KeycloakSession session) { 29 | log.debug("Authenticator: custom-attribute-idp-linking created."); 30 | return SINGLETON; 31 | } 32 | 33 | @Override 34 | public void init(Config.Scope config) { 35 | } 36 | 37 | @Override 38 | public void postInit(KeycloakSessionFactory factory) { 39 | } 40 | 41 | @Override 42 | public void close() { 43 | } 44 | 45 | @Override 46 | public String getId() { 47 | return PROVIDER_ID; 48 | } 49 | 50 | @Override 51 | public String getReferenceCategory() { 52 | return "idp-auto-linking"; 53 | } 54 | 55 | @Override 56 | public boolean isConfigurable() { 57 | return true; 58 | } 59 | 60 | @Override 61 | public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { 62 | return REQUIREMENT_CHOICES; 63 | } 64 | 65 | @Override 66 | public String getDisplayType() { 67 | return "Custom Attribute IDP Linking"; 68 | } 69 | 70 | @Override 71 | public String getHelpText() { 72 | return "Lookup exiting user by custom Attribute"; 73 | } 74 | 75 | @Override 76 | public List getConfigProperties() { 77 | return ProviderConfigurationBuilder.create() 78 | .property().name(CONFIG_IDP_ATTRIBUTE) 79 | .type(ProviderConfigProperty.STRING_TYPE) 80 | .label("Identity provider user attribute") 81 | .helpText("Attribute from identity provider to match against existing users.") 82 | .defaultValue("user.attributes.eid") 83 | .add() 84 | .property().name(CONFIG_LOOKUP_ATTRIBUTE) 85 | .type(ProviderConfigProperty.STRING_TYPE) 86 | .label("Lookup Attribute") 87 | .helpText("User attribute used to compare to identity provider attribute.") 88 | .defaultValue("eid") 89 | .add() 90 | .property().name(CONFIG_FAIL_ON_NO_MATCH_ATTRIBUTE) 91 | .type(ProviderConfigProperty.BOOLEAN_TYPE) 92 | .label("Fail on no match") 93 | .helpText("User attribute make linker fail if there is no matching user.") 94 | .defaultValue(false) 95 | .add() 96 | .build(); 97 | } 98 | 99 | @Override 100 | public boolean isUserSetupAllowed() { 101 | return true; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory: -------------------------------------------------------------------------------- 1 | foundation.softwaredesign.keycloak.authenticators.CustomAttributeIdpLinkingAuthenticatorFactory --------------------------------------------------------------------------------