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 (create a service account to use Cloud-IAM REST 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

How to upload a custom extension on Cloud-IAM via API

It is possible to upload custom extensions through Cloud-IAM API. To start, you will need to get the deployment id you want to interact with. This can be found in the Cloud-IAM Console, near your deployment name.

Get the deployment id
Get the deployment id

Assuming you have built an extension available in /home/user/projects/cloud-iam/extension/target/extension.jar.

shell
DEPLOYMENT_ID=xxxxx
curl -X POST -F extension=@/home/user/projects/cloud-iam/extension/target/extension.jar \
     -H "Authorization: Bearer $TOKEN" \
     https://api.cloud-iam.com/deployments/${DEPLOYMENT_ID}/extensions/jars
DEPLOYMENT_ID=xxxxx
curl -X POST -F extension=@/home/user/projects/cloud-iam/extension/target/extension.jar \
     -H "Authorization: Bearer $TOKEN" \
     https://api.cloud-iam.com/deployments/${DEPLOYMENT_ID}/extensions/jars

This will create a new resource attached to the deployment and will trigger automatically the deployment of the extension on the cluster. During this period, no further interaction with the deployment are possible.

TIP

If you need to batch the upload of multiple extension before re-deploying it, simply add ?apply=false at the end of the url to skip the automatic redeployment.

Once you are ready with the configuration / upload of extensions, call the following url to eventually apply all the changes.

shell
DEPLOYMENT_ID=xxxxx
curl -X POST -F content=@/home/user/projects/cloud-iam/extension/target/extension.jar \
     -H "Authorization: Bearer $TOKEN" \
     https://api.cloud-iam.com/deployments/${DEPLOYMENT_ID}/tasks/deploy
DEPLOYMENT_ID=xxxxx
curl -X POST -F content=@/home/user/projects/cloud-iam/extension/target/extension.jar \
     -H "Authorization: Bearer $TOKEN" \
     https://api.cloud-iam.com/deployments/${DEPLOYMENT_ID}/tasks/deploy