Skip to content

Use of custom extensions

Keycloak is designed to cover most use-cases without requiring custom code, but we also want it to be customizable. To achieve this Keycloak has a number of Service Provider Interfaces (SPI) for which you can implement your own providers. Custom providers play a key role in Keycloak's architecture. For every major functionality, like the login flow, authentication, authorization, there's a corresponding Service Provider Interface. This approach allows us to plug custom implementations for any of those services, which Keycloak will then use as it were one of its own.

These providers must be implemented in Java programming language and compiled as jar file to be then uploaded to your deployment through Cloud-IAM dashboard or Cloud-IAM API. In its simplest form, a custom provider is just a standard jar file containing one or more service implementations.

A common requirement, especially when legacy systems are involved, is to integrate users from those systems into your Keycloak deployment.

shell
$ curl --location --request POST 'https://api.cloud-iam.com/deployments/$DEPLOYMENT_ID/extensions/jars' \
       --header 'Authorization: Bearer $ACCESS_TOKEN' \
       --form 'extension=@example.jar' \
$ curl --location --request POST 'https://api.cloud-iam.com/deployments/$DEPLOYMENT_ID/extensions/jars' \
       --header 'Authorization: Bearer $ACCESS_TOKEN' \
       --form 'extension=@example.jar' \

Troubleshooting

502 Bad Gateway

If an uploaded custom extension throw an uncaught exception (e.g. Null Pointer Exception) the Keycloak node that received the HTTP request will yield a 500 http response.

Cloud-IAM expose all nodes of the Keycloak cluster deployment behind a load-balancer. When the load-balancer gets a 5xx response from one of the Keycloak nodes it will retry the request on another node until none are available because none can successfully (2xx) reply to the HTTP request. In that case Cloud-IAM load-balancer will yield a 502 http response.

Complete stack-trace and details of the custom extension uncaught exception is available in the deployment Keycloak logs.

Cloud-IAM team recommend to setup a separate Keycloak cluster environment to automatically or manually check the quality and integration of your custom extension with Keycloak. This is because Keycloak cannot isolate custom extension loading per realm.

Caused by: java.lang.ClassNotFoundException

If the extension relies on a Keycloak class, this can lead to an error during the start of the extension such as Caused by: java.lang.ClassNotFoundException: org.keycloak.services.managers.XXXXXX because on Quarkus the class loaders are isolated for safety reasons.

This issue can be resolved by declaring explicitly the dependencies needed by the extension in the MANFIEST.MF file.

If for instance, the extension's pom.xml contains such a dependency:

xml
<dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-services</artifactId>
        <scope>provided</scope>
        <version>${keycloak.version}</version>
    </dependency>
<dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-services</artifactId>
        <scope>provided</scope>
        <version>${keycloak.version}</version>
    </dependency>

Then the following declaration must follow to tell Quarkus to share the classes between the server and the extension.

xml
<plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-jar-plugin</artifactId>
       <configuration>
           <archive>
               <manifestEntries>
                   <Dependencies>org.keycloak.keycloak-services</Dependencies>
               </manifestEntries>
           </archive>
       </configuration>
    </plugin>
<plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-jar-plugin</artifactId>
       <configuration>
           <archive>
               <manifestEntries>
                   <Dependencies>org.keycloak.keycloak-services</Dependencies>
               </manifestEntries>
           </archive>
       </configuration>
    </plugin>

Resources