Keycloak - how to allow linking accounts without registration

Viewed 9532

I am managing a Keycloak realm with only a single, fully-trusted external IdP added that is intended to be the default authentication mechanism for users.

I do not want to allow user to register, i.e. I want to manually create a local Keycloak user, and that user should then be allowed to link his external IdP account to the pre-existing Keycloak account, having the email address as common identifier. Users with access to the external IdP but without an existing Keycloak account should not be allowed to connect.

I tried the following First Broker Login settings, but whenever a user tries to login, he gets an error message (code: invalid_user_credentials).

enter image description here

Do you have any idea what my mistake might be?

5 Answers

Looks like they integrated this feature in version 4.5.0.

See automatic account link docs.

Basically you need to create a new flow and add 2 alternative executions:

  1. Create User If Unique

  2. Automatically Link Brokered Account

As per this discussion:

https://keycloak.discourse.group/t/link-idp-to-existing-user/1094/5

It’s a bug in keycloak and they seem to be a reluctant to fix it for whatever reason. I have very few users so I solved it by manually querying the idp for the information keycloak uses and then copying it into the relevant fields in the UI. So there is no sign up process for my users I just make them myself. Obviously that’s a poor solution though, what we really need is someone to take over that PR and persuade the maintainers to merge it.

This is the PR: https://github.com/keycloak/keycloak/pull/6282

As it is described in this GitHub issue response the solution is to use a JavaScript authenticator that handles this.

In order to do so, you need to do the folowing:

  1. Enable [custom authenticators using JavaScript in your server[(https://www.keycloak.org/docs/latest/server_installation/#profiles) by https://stackoverflow.com/a/63274532/550222creating a file profile.properties in your configuration directory that contains the following:

     feature.scripts=enabled
    
  2. Create the custom authenticator. You have to create a JAR file (essentially a ZIP file) with the following structure:

     META-INF/keycloak-scripts.json
     auth-user-must-exist.js
    

    The content of the files are in this Gist, but I am including them here as well:

    • META-INF/keycloak-scripts.json:

      {
          "authenticators": [
              {
                  "name": "User must exists",
                  "fileName": "auth-user-must-exists.js",
                  "description": "User must exists"
              }
          ]
      }
      
    • auth-user-must-exist.js:

      AuthenticationFlowError = Java.type("org.keycloak.authentication.AuthenticationFlowError")
      ServicesLogger = Java.type("org.keycloak.services.ServicesLogger")       
      AbstractIdpAuthenticator = Java.type("org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator")
      IdpCreateUserIfUniqueAuthenticator = Java.type("org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticator")
      
      
      var IdpUserMustExists = Java.extend(IdpCreateUserIfUniqueAuthenticator)
      
      
      function authenticate(context) {                                         
          var auth = new IdpUserMustExists() {                                 
              authenticateImpl: function(context, serializedCtx, brokerContext) {
                  var parent = Java.super(auth)                                
      
                  var session = context.getSession()                           
                  var realm = context.getRealm()                               
      
                  var authSession = context.getAuthenticationSession()         
      
                  if (authSession.getAuthNote(AbstractIdpAuthenticator.EXISTING_USER_INFO) != null) {
                      context.attempted()                                      
                      return                                                   
                  }                                                            
      
                  var username = parent.getUsername(context, serializedCtx, brokerContext)
                  if (username == null) {                                      
                      ServicesLogger.LOGGER.resetFlow(realm.isRegistrationEmailAsUsername() ? "Email" : "Username")
                      authSession.setAuthNote(AbstractIdpAuthenticator.ENFORCE_UPDATE_PROFILE, "true")
                      context.resetFlow()                                      
                      return                                                   
                  }                                                            
      
                  var duplication = parent.checkExistingUser(context, username, serializedCtx, brokerContext)
                  if (duplication == null) {                                   
                      LOG.info("user not found " + username)                   
                      context.failure(AuthenticationFlowError.INVALID_USER) 
                      return                                                   
                  } else {                                                     
                      authSession.setAuthNote(AbstractIdpAuthenticator.EXISTING_USER_INFO, duplication.serialize())
                      context.attempted()                                      
                  }                                                            
              }                                                                
          }                                                                    
      
          auth.authenticate(context)                                           
      }
      
  3. Then, you can define as follows:

    • User Must Exist -> ALTERNATIVE
    • Automatically Set Existing User -> ALTERNATIVE

Honestly i am surprised by the keycloak auto creating behavior. I tried to add new Authentication flow as descibed here https://www.keycloak.org/docs/latest/server_admin/index.html#automatically-link-existing-first-login-flow

My flow :

1 - Create User If Unique [ALTERNATIVE]

2 - Automatically Link Brokered Account [ALTERNATIVE]

My use case : Authenticating users from Github ( Github as IDP )

Result : when a github user logon with an existing "username" keycloak links the github account to my local user ( based on his username ). I expected using his email instead of username.

Related