Building a custom conditional authenticator for Keycloak

Building a custom conditional authenticator for Keycloak

In the Karelics Cloud, we use Keycloak as the identity management tool. Keycloak provides many customization options, but we have a scenario where the out-of-the-box solutions are insufficient: if the user has the single sign-on (SSO) configured with an external identity provider, it does not allow the user to log in with a username and password. This is useful if their organization maintains the external user account: when the organization’s admin deactivates the user in their system, the access to Karelics Cloud is also deactivated.

Typically, a specialized authentication process can be achieved with a custom authentication flow, but the built-in flow actions do not allow checking whether the user has SSO configured. We also want to keep the possibility of logging in with a username and password for users without SSO.

Luckily, Keycloak allows the implementation of extensions (in Java) that work by providing alternative implementations to the interfaces Keycloak uses.

We have decided to make a new conditional authenticator for Keycloak to build the desired custom browser flow. The Conditional sub-flow uses conditional authenticators to check if the body of the sub-flow needs to be executed.

In typical programming languages, the conditional statement typically has a structure along the lines of:

				
					if *condition* then
    *execute_body*
				
			

In Keycloak flows, this is presented differently:

First, you have the conditional sub-flow with just a name; underneath it, you have the conditions and actions to be executed. In the following example, the conditional sub-flow is named “Check if SSO configured (form login)”. It checks the condition “Condition – SSO configured”, and if it is satisfied, executes the action “Deny access” (which rejects the authentication for the user).

“Condition – SSO configured” is the new conditional authenticator we have implemented as a Keycloak extension. Adding this conditional sub-flow to the browser authentication flow would deny users access with login and password if they have SSO configured.

Thus we can have the following authentication flows to satisfy our requirements:

  1. Browser flow is customized to deny access if the user has SSO configured.
  2. The same is done for the direct grant flow.
  3. Identity Provider uses a custom first login flow (without the conditional authenticator) and handles the authentication if the user has SSO configured.

Let’s have a look at the implementation of the conditional authenticator.

At a minimum, the typical Keycloak SPI (service provider interface) extension is a Java project consisting of three files:

  • the main class implementing or extending a Keycloak interface/class
  • a factory class providing instances of the main class + additional metadata about it (like configuration parameters)
  • META-INF.services service provider configuration file, allowing Keycloak to discover the factory class

In our case, we have created the main class by implementing

org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticator

and the factory class by implementing

org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticatorFactory.

The actual check for SSO configuration is the following:

				
					private boolean getConditionValue(AuthenticationFlowContext context){
	AuthenticationSessionModel authSession = context.getAuthenticationSession();
	UserModel user = context.getUser();
	var session = context.getSession();
	var sessionIdentities = session.users().getFederatedIdentitiesStream(authSession.getRealm(), user);
	var identitiesCount = sessionIdentities.count();
	sessionIdentities.close();
	return identitiesCount > 0;
}
				
			

The extension has a single configuration parameter, Negate output, which reverses the condition if set to true (i.e., the condition would be true if the user doesn’t have SSO configured).

We have published the source code for our extension, ConditionalIdentityAuthenticator, on our GitHub.

Facebook
Twitter
LinkedIn
Danil Vilaev
Danil Vilaev

Karelics Oy, backend developer (Karelics Cloud)

Over 12 years of experience as a .NET developer, now focusing on ASP.NET Core development.

Worked as a contractor software developer for several large Finnish companies, developing both customer-facing software and in-house business process automation. Also worked as a contractor service engineer for an industrial process automation company.

All Posts