What is Keycloak?

Keycloak is based on the OAuth 2.0 and OpenID Connect standards and enables the integration of a central authentication and authorisation layer in applications. Keycloak can be used to manage accounts, validate their identities via various external identity providers (IdPs) and provide single sign-on (SSO) for multiple applications. Keycloak supports various authentication methods such as user name/password, Social Login via platforms such as Google, Facebook and GitHub, SAML 2.0 and more. Keycloak also offers the possibility to define and use customised attributes to store additional information about accounts.

If you want to find out more about the basics of Keycloak, I recommend reading the blog post on ‘Access rights management with Keycloak as your IAM system’.

One of Keycloak’s greatest strengths is its flexibility and expandability. And this is exactly where this blog post comes in. By default, Keycloak only allows login by user name and/or e-mail address. But what if this is not enough and another unique feature is to be used as a login, such as a telephone or customer number?

Fundamentals

Keycloak provides a corresponding ‘browser flow’ if accounts are to log in to an application secured by Keycloak via a browser. This flow presents a ‘UsernamePasswordForm’ in which the login data – in the default case: user name/e-mail and password – are entered and, if successful, the account is forwarded to the secure application as authenticated.

Now if we want to ensure that accounts can also log in with additional unique information such as a customer number, because the project has this as a requirement due to a legacy data migration, for example, we have to add our specific logic to this ‘UsernamePasswordForm’.

Creating test users with customer number

For the example, I first created a user via the Keycloak Admin interface and assigned a customer number to it as an attribute:


View of the user attributes

With this customer number we can log in after the adjustments and extensions described below. In a real project, this customer number should be automatically linked to the account during registration, either by Keycloak or by an external system.

Extension of the ‘UsernamePasswordForm’

This form is used in the browser authentication flow to evaluate the login fields for user name/e-mail address.


View of the configured browser authentication flow

This is where we want to start and ensure that a valid customer number in conjunction with the correct password also leads to a successful login attempt. Keycloak provides the ‘UsernamePasswordForm’ method for this purpose. We can extend this by overwriting the ‘validateUserAndPassword’ method from the abstract class ‘AbstractUsernameFormAuthenticatot’. This gives us the opportunity to intervene in the validation of the values entered in the login screen. This allows us to check whether the login name is an existing customer number. To do so, we search the current realm for all accounts that have saved this value as an attribute in their account:

	
		List<UserModel> users = context.getSession().users()
		        .searchForUserByUserAttributeStream(
				context.getRealm(), "customer_number", username)
		        .toList();
	

It must be expected that several accounts with the customer number may be found if this has not been correctly prevented beforehand. This case must be treated accordingly. If no account is found, you can check whether the value entered is a user name:

	
		user = context.getSession().users()
			.getUserByUsername(context.getRealm(), username);
	

If no account is found here either, the last step is to check whether the value is an e-mail address. But watch out, it is only possible to search for the e-mail if we have activated the corresponding setting in the realm and therefore login by e-mail is permitted:


E-mail settings at realm level in the ‘Login’ tab

	
		if (user == null && context.getRealm().isLoginWithEmailAllowed()) {
		    user = context.getSession().users()
				.getUserByEmail(context.getRealm(), username);
		}
	

If we still have not found an account, we can say with certainty that the value entered does not exist as a customer number, user name or e-mail. In this case, we can cancel the registration and display a corresponding error message on the registration page:

	
		setInvalidUserError(context);
		return false;
		private void setInvalidUserError(AuthenticationFlowContext context) {
		    Response challengeResponse = challenge(context, Messages.INVALID_USER);
		    context.failureChallenge(
			AuthenticationFlowError.INVALID_USER, challengeResponse);
		}
	

Once we have found an account, we still need to ensure that the password is validated accordingly. This is normally done in the overwritten method of the superclass. If we did not do this here, the user would be successfully logged in, even if they entered an invalid password for the account.

	
		if (user == null || !validatePassword(context, user, inputData, false)) {
		    setInvalidUserError(context);
		    return false;
		}
	

It is important that we use the same error message for both an incorrect password and an incorrect user name. This prevents an attacker from using the error messages to find out whether he has found a valid account.

If the password is also valid, we can assume that the user has successfully logged in:

	
		context.setUser(user);
		return true;
	

The entire customisation of the ‘UsernamePasswordForm’ can be found again here.

Configuration of the authentication flow in Keycloak

However, before these changes take effect in Keycloak, it is necessary to provide the Factory class for our customisations in a MANIFEST file. To do so, we create the following file and add our factory to it:

	
		de.adesso.CustomerNumberAuthenticatorFactory
	

We also need to adjust the configuration of the browser flow. To do so, we copy the default browser flow and adapt the copy as follows so that our new form is used:


Setting the new customised browser authentication flow to use customer number login

The copy must be set as the new standard for the browser flow. Everything is now ready so that the accounts can also log in using the stored customer number.

Test login via customer number

To test our customisations, we can simply call up the link to the Keycloak account administration (http://localhost:8080/realms/adesso-blog/account/) and try to log in there with the customer number stored for the test account:


Login via the stored customer number

If this was successful, we can now access the relevant account management menus and make changes to our profile.


Management of the account after successful login

Note:

The user must have the ‘manage-account’ role and the account client must have the available client scopes to ensure that a ‘Forbidden’ message is not the only thing displayed here. In both cases, however, the user was successfully logged in (authenticated) using their customer number.

Conclusion

We have made it possible for accounts to log in with project-specific features with this simple extension to Keycloak, in addition to the standard login features. This can also be a telephone number, for example, instead of a customer number.

The entire sample project can be found again at the following link: https://github.com/boehmalex/keycloak-customer-number-login. The project is based on the current version 23.0.3 of Keycloak at the time of publication of this post.

Would you like to learn more about exciting topics from the adesso world? Then take a look at our latest blog posts.

Picture Alexander  Böhm

Author Alexander Böhm

Alexander Böhm works as a senior software engineer in the sports department at adesso and has already supervised and implemented Keycloak integrations in various projects. He is also a passionate and dedicated software architect.

Save this page. Remove this page.