Hi everyone,
I am working on creating a User Storage SPI for my Keycloak setup with the aim of migrating users from my external users table (created using legacy auth) into Keycloak. Here’s the flow I am aiming for:
- A user attempts to log in to my platform via Google.
- Keycloak first searches its internal database for the user.
- If the user is found, it returns the user.
- If the user is not found, it searches the external database.
- If the user is found in the external database, it adds the user to Keycloak’s internal database and then returns the user.
- If the user is not found in the external database, it creates a new user in both Keycloak’s internal database and the external database. (I need to maintain the external users table, which is why new users are also added there.)
From my understanding of the SPI, whenever a social login occurs, getUserByEmail
is triggered. I have added logic in this method to search for the user in Keycloak’s internal database and the external database, and to create the user if necessary. Once getUserByEmail
returns a Keycloak user object, I would expect the flow to be complete.
However, the issue I’m facing is that when a user already exists in the external database, after getUserByEmail
returns a valid UserModel
(i.e., the user is found in the external database and created in Keycloak’s internal database), Keycloak seems to be creating the social user again. This results in a duplicate account screen and a prompt to link the accounts.
Here’s my getUserByEmail
code
// Keycloak calls this function only after it's not able to find the user in it's internal DB
// Searching for the user in external DB
CriteriaQuery<ExternalUserModel> query = createCriteriaQuery(AppConstants.EMAIL, email);
try {
ExternalUserModel user = null;
try {
user = externalDbSession.createQuery(query).getSingleResult();
} catch (NoResultException nre) {
// User not found in external DB, returning null so that Keycloak can invoke addUser which will take care of user creation in both Keycloak and External DB.
return null;
}
// create user in Keycloak
UserModel user = UserStoragePrivateUtil.userLocalStorage(session).addUser(realm, externalUser.getId(), transformedUsername, true, false);
user.setFederationLink(model.getId());
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel("google", externalUser.getGoogleId(), externalUser.getEmail());
session.users().addFederatedIdentity(realm, user, federatedIdentityModel);
user.setEmail(externalUser.getEmail());
user.setEnabled(true);
return user;
} catch (SQLException e) {
return null;
}
Any help will be appreciated. Am I missing something, or have I misunderstood something about the user storage SPI ?