iron's blog

Using Keycloak with existing legacy user accounts

Keycloak is an tool that allows you to manage the authentication and authorization for many applications. Using an application like keycloak is often a good idea because rolling your own authentication provider is a security nightmare, while the existing solutions are well-maintained and battle tested. Keycloak also makes it trivial to let users login using other accounts they may already have, such as Google or Twitter.

If you are developing an environment, using Keycloak is extremely easy and convenient; the hardest part is actually styling the keycloak pages to match your application(s). However, when you are integrating keycloak into an environment where user-accounts are already defined and stored somewhere is a totally different story.

In my case, users were stored in a MSSQL database and managed using an old legacy WinForms application with mountains of technical debt. The WinForms application is of a significant size, and many tools/plugins depend on it; so its staying around for a while. Updating the WinForms app to support authentication handled by a third party such as Keycloak would be a nightmare. So Keycloak needed to be able to support the users stored in this MSSQL database.

Ofcourse, this problem is not unique; and many Keycloak users have the same problem. That is why Keycloak supports User Federation. By default Keycloak has support for two types of external user storage, LDAP and Kerberos. However my users were stored using a custom solution with no support for any standard protocol. That is why I needed to roll my own keycloak SPI to add support.

Creating a keycloak SPI

Keycloak SPI’s allow adding custom functionality to Keycloak, this functionality can range from adding API endpoints, sending emails, creating custom themes to custom user providers. And the last part is exactly what I need. SPI’s are written in Java (or another JVM compatible language), and adding them to keycloak is as simple as placing a .jar file in the right place. Many SPI examples can be seen here.

The Java code only needs to implement a few interfaces for them to fully interact with keycloak. My custom storage SPI looks as follows:

Where the factory (needs to be registered in the META-INF) configures and supplies keycloak with a UserProvider, which then uses a (custom) repository and supplies keycloak with UserAdapters. This is all the code that is needed. Keycloak sends all events (search user, validate credentials, get user info) to the UserProvider, which is then free to do whatever it wants. As a minimal example, if you make the isValid return true for any input, keycloak will accept any logins and create sessions for any login info its given.

For example, my IsValid method looks as follows:

@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
    logger.infov("isValid user credential: userId={0}", user.getId());

    if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) {
        return false;
    }

    UserCredentialModel cred = (UserCredentialModel) input;
    UserModel dbUser = user;
    return repository.validateCredentials(dbUser.getUsername(), cred.getChallengeResponse());
}

Adding custom federation provider settings is also very easy using the ProviderConfigurationBuilder that is available in your registerd Factory class.

public List<ProviderConfigProperty> getConfigProperties() {
    return ProviderConfigurationBuilder.create()
            .property()
            .name("url")
            .label("DB Generic URL")
            .type(ProviderConfigProperty.STRING_TYPE)
            .defaultValue("jdbc:jtds:sqlserver://server/nori_db")
            .helpText("A JDBC Connection string to the generic database")
            .add()

            .property()
            .name("user")
            .label("DB User")
            .type(ProviderConfigProperty.STRING_TYPE)
            .defaultValue("sa")
            .helpText("Database user")
            .add()

            .property()
            .name("password")
            .label("DB Password")
            .type(ProviderConfigProperty.PASSWORD)
            .helpText("Database password")
            .add()
            .build();
}

Getting the SPI into keycloak

Assuming your keycloak is running using Docker (or Podman, K8s, Nomad, you name it), “simply copying a .jar file into the correct directory” may not be the most elegant solution. Because that would require setting up volumes, ways for admins to reach that directory (SFTP?) and maintaining the version of your SPI with the version of keycloak manually. Extending the Keycloak docker image is often a better solution (assuming that you are already running OCI image repository). This is trivially done using a multi-staged Dockerfile, consider the following example

FROM maven:3.9.2-eclipse-temurin-11 as build
COPY ./UserProviderSPI /opt/UserProviderSPI
RUN cd /opt/UserProviderSPI && mvn clean package

FROM quay.io/keycloak/keycloak:21.1
COPY --from=build /opt/UserProviderSPI/target/dist/* /opt/keycloak/providers/
COPY realm-export.json /opt/keycloak/data/import/realm.json

Thats it, now just build the image and run it, it will contain your custom user adapter.

$ docker build -t nori-keycloak:21.1 .
$ docker stack deploy keycloak -c {your stack definition here}.yml

And there you go, a custom user-federation SPI in keycloak.

Add provider screen
Add provider screen
Thank you for reading this article.
If you spot any mistakes or if you would like to contact me, visit the contact page for more details.