This project provides a SCIM 2.0-compliant extension for Keycloak, enabling SCIM-based user and group provisioning. It supports:
- Realm-level SCIM APIs:
/realms/{realm}/scim/v2 - Organization-level SCIM APIs (Keycloak 26+ with Organizations):
/realms/{realm}/scim/v2/organizations/{organizationId}
- Prerequisites
- Installation
- Configuration
- Authentication
- Vendor Configuration Guides
- Identity Provider Linking
- SCIM-Managed Users
- License
- Keycloak: This extension is developed for Keycloak 26.3.5. It may work with other versions, but compatibility is not guaranteed.
- Java: Java 21 is required to build the project.
You can reference the JAR file from a GitHub Release directly in your init container or Dockerfile.
For example, using a Helm values.yaml:
extraInitContainers: |
- name: download-scim-plugin
image: alpine:latest
command:
- sh
- -c
- >
apk add --no-cache curl &&
curl -L -o /extensions/keycloak-scim-server-<version>.jar https://github.com/Metatavu/keycloak-scim-server/releases/download/v<version>/keycloak-scim-server-<version>.jar
volumeMounts:
- name: extensions
mountPath: /extensions
extraVolumeMounts: |
- name: extensions
mountPath: /opt/keycloak/providers
extraVolumes: |
- name: extensions
emptyDir: {}Download the JAR file from GitHub packages.
- Download the latest JAR from: GitHub Packages
- Copy it to your Keycloak instance:
cp keycloak-scim-server-*.jar $KEYCLOAK_HOME/providers/- Restart Keycloak.
- Build the extension:
./gradlew build- Copy the built JAR file from
build/libs/keycloak-scim-server-<version>.jarto the Keycloak providers directory:
cp build/libs/keycloak-scim-server-*.jar $KEYCLOAK_HOME/providers/All settings can be applied at three levels. Settings at a more specific level override broader ones (organization > realm > instance).
Configuration on instance level is done by defining environment variables on the Keycloak server.
| Setting | Description |
|---|---|
SCIM_AUTHENTICATION_MODE |
Authentication mode for SCIM API. Possible values are KEYCLOAK and EXTERNAL. If not set the server will respond unauthorized for all requests. |
SCIM_EXTERNAL_ISSUER |
Issuer for external JWT authentication. Used to validate the JWT token issuer claim. |
SCIM_EXTERNAL_AUDIENCE |
Audience for external JWT authentication. Used to validate the JWT token audience claim. |
SCIM_EXTERNAL_JWKS_URI |
JWKS URI for external JWT authentication. Used to fetch public keys for JWT token signature validation. |
SCIM_EXTERNAL_SHARED_SECRET |
Shared secret in PHC String Format used for bearer token authentication/validation. |
SCIM_BASIC_AUTH_USERNAME |
Username for HTTP Basic authentication. |
SCIM_BASIC_AUTH_PASSWORD |
Password hash in PHC String Format for HTTP Basic authentication. |
SCIM_LINK_IDP |
Enables support for linking realm identity provider with user. |
SCIM_IDENTITY_PROVIDER_ALIAS |
Alias of Identity Provider to be linked to the user. |
The following REST call can be made through the Keycloak Admin API to store settings as realm attributes. Realm-level settings override instance-level settings.
PUT /admin/realms/{realm}
{
"attributes": {
"scim.authentication.mode": "EXTERNAL|KEYCLOAK",
"scim.external.issuer": "string",
"scim.external.jwks.uri": "string",
"scim.external.audience": "string",
"scim.external.shared.secret": "string",
"scim.basic.auth.username": "string",
"scim.basic.auth.password": "string",
"scim.link.idp": "true|false",
"scim.identity.provider.alias": "string"
}
}Configuration on organization level is done by defining organization attributes in the Keycloak server. Only EXTERNAL authentication mode is supported at the organization level.
| Setting | Description |
|---|---|
SCIM_AUTHENTICATION_MODE |
Must be EXTERNAL. Only external authentication is supported at the organization level. |
SCIM_EXTERNAL_ISSUER |
Issuer for external JWT authentication. |
SCIM_EXTERNAL_AUDIENCE |
Audience for external JWT authentication. |
SCIM_EXTERNAL_JWKS_URI |
JWKS URI for external JWT authentication. |
SCIM_EXTERNAL_SHARED_SECRET |
Shared secret in PHC String Format for bearer token authentication. |
SCIM_BASIC_AUTH_USERNAME |
Username for HTTP Basic authentication. |
SCIM_BASIC_AUTH_PASSWORD |
Password hash in PHC String Format for HTTP Basic authentication. |
SCIM_LINK_IDP |
Enables support for linking organization identity provider with user. |
SCIM_EMAIL_AS_USERNAME |
Forces server to use email as username instead of actual username. When enabled, username will be unaffected by update operations. Organization-level only. |
The SCIM server supports four authentication methods. For EXTERNAL mode, the method is determined automatically based on the authorization header and configuration.
Uses Keycloak's built-in service account authentication. The SCIM client authenticates using an OAuth2 client credentials flow against Keycloak itself, and the resulting access token is validated natively.
Required settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
KEYCLOAK |
Requirements:
- A Keycloak client with Service Accounts Enabled
- The client's service account must have the
scim-accessrealm role
Example: The SCIM client obtains a token via the Keycloak token endpoint and sends it as a bearer token:
Authorization: Bearer <keycloak-access-token>
Validates a JWT bearer token issued by an external identity provider. The token signature is verified against public keys fetched from the configured JWKS endpoint, and the issuer and audience claims are validated.
Required settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
EXTERNAL |
SCIM_EXTERNAL_ISSUER |
Expected iss claim (e.g., https://sts.windows.net/<tenant-id>/) |
SCIM_EXTERNAL_AUDIENCE |
Expected aud claim |
SCIM_EXTERNAL_JWKS_URI |
URL to the JWKS endpoint for public keys |
Example request:
Authorization: Bearer <jwt-token>
Validates a static bearer token against a pre-configured hash. The client sends the raw secret as a bearer token, and the server verifies it against the stored hash using Keycloak's password hashing infrastructure.
Required settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
EXTERNAL |
SCIM_EXTERNAL_SHARED_SECRET |
Hashed token in PHC String Format (e.g., Argon2id) |
The hash must be in PHC String Format using a Keycloak-supported hash algorithm (e.g., argon2id, pbkdf2-sha512).
Example: If the shared secret is my-secret-token, hash it using Argon2id and configure the resulting PHC string:
SCIM_EXTERNAL_SHARED_SECRET=$argon2id$v=19$m=16,t=2,p=1$<salt>$<hash>
The client then sends the raw secret:
Authorization: Bearer my-secret-token
Validates credentials sent via HTTP Basic Authentication. The client sends a Base64-encoded username:password pair, and the server verifies the username against the configured value and the password against a stored hash.
Required settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
EXTERNAL |
SCIM_BASIC_AUTH_USERNAME |
Expected username |
SCIM_BASIC_AUTH_PASSWORD |
Password hash in PHC String Format (e.g., Argon2id) |
The password hash must be in PHC String Format using a Keycloak-supported hash algorithm.
Example: If the username is scim-admin and password is my-password, hash the password using Argon2id and configure:
SCIM_BASIC_AUTH_USERNAME=scim-admin
SCIM_BASIC_AUTH_PASSWORD=$argon2id$v=19$m=16,t=2,p=1$<salt>$<hash>
The client then sends:
Authorization: Basic <base64("scim-admin:my-password")>
Entra ID supports two authentication options when provisioning to this SCIM server:
Entra ID sends a bearer token signed by Microsoft's identity platform. The SCIM server validates it using Microsoft's JWKS endpoint.
Keycloak settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
EXTERNAL |
SCIM_EXTERNAL_ISSUER |
https://sts.windows.net/<your-tenant-id>/ |
SCIM_EXTERNAL_AUDIENCE |
8adf8e6e-67b2-4cf2-a259-e3dc5476c621 |
SCIM_EXTERNAL_JWKS_URI |
https://login.microsoftonline.com/<your-tenant-id>/discovery/v2.0/keys |
Replace <your-tenant-id> with your Azure tenant ID. The audience 8adf8e6e-67b2-4cf2-a259-e3dc5476c621 is the default used by Entra ID for non-gallery applications.
Entra ID sends a static bearer token that you generate and configure on both sides.
Keycloak settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
EXTERNAL |
SCIM_EXTERNAL_SHARED_SECRET |
<token_hashed_value> |
Replace <token_hashed_value> with the PHC String Format hash of your token.
- Sign in to the Azure portal.
- Go to Identity > Applications > Enterprise applications.
- Click + New application > + Create your own application.
- Enter a name for your application (e.g., "My Keycloak SCIM").
- Choose Integrate any other application you don't find in the gallery.
- Click Create.
- In the application's left-hand menu, select Provisioning.
- Click + New configuration.
- Fill in the following:
- Tenant URL (realm):
https://mykeycloak.example.com/realms/my-realm/scim/v2 - Tenant URL (organization):
https://mykeycloak.example.com/realms/my-realm/scim/v2/organizations/{organizationId} - Secret Token: Leave empty for JWT authentication (Option A), or enter the raw shared secret (Option B).
- Tenant URL (realm):
- Click Test Connection to verify the SCIM endpoint.
- Click Create.
- Navigate to Attribute Mapping (Preview).
- Open Provision Microsoft Entra ID Groups.
- Set Enabled to No.
- Click Save.
- Go back and open Provision Microsoft Entra ID Users.
- Define mappings. The following are required:
userNameactiveemails[type eq "work"].valuename.givenNamename.familyName
- Click Save.
- Go back to Provisioning.
- Set Provisioning Status to On.
- Click Save.
- Reload the page to ensure the configuration was saved.
- Navigate to Manage > Users and groups > + Add user/group.
- Select the user you want to provision and click Assign.
- Navigate to Provision on demand.
- Find the user you just assigned.
- Click on the user and select Provision.
- Verify that the provisioning completes successfully.
For more information, refer to the Microsoft Entra ID SCIM provisioning documentation.
Okta supports three authentication methods when provisioning to a SCIM server. All three are supported by this extension.
For general Okta SCIM setup, refer to the Okta SCIM provisioning documentation.
Okta sends a static bearer token in the Authorization header. This uses the shared secret authentication method.
Keycloak settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
EXTERNAL |
SCIM_EXTERNAL_SHARED_SECRET |
PHC String Format hash of your API token |
Okta setup:
- In the Okta Admin Console, go to Applications > Applications.
- Select your SCIM application.
- Go to the Provisioning tab and click Configure API Integration.
- Select HTTP Header as the authentication mode.
- In the Authorization field, paste the raw API token (the unhashed value).
- Set the SCIM connector base URL to:
https://mykeycloak.example.com/realms/my-realm/scim/v2 - Click Test API Credentials to verify.
- Click Save.
Okta sends credentials via HTTP Basic Authentication (Base64-encoded username:password).
Keycloak settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
EXTERNAL |
SCIM_BASIC_AUTH_USERNAME |
The username you want Okta to authenticate with |
SCIM_BASIC_AUTH_PASSWORD |
PHC String Format hash of the password |
Okta setup:
- In the Okta Admin Console, go to Applications > Applications.
- Select your SCIM application.
- Go to the Provisioning tab and click Configure API Integration.
- Select Basic Auth as the authentication mode.
- Enter the Username and Password (the raw, unhashed values).
- Set the SCIM connector base URL to:
https://mykeycloak.example.com/realms/my-realm/scim/v2 - Click Test API Credentials to verify.
- Click Save.
Okta obtains an access token from an OAuth2 token endpoint, then sends it as a bearer token. Since Keycloak is itself an OAuth2 provider, you can point Okta at Keycloak's token endpoint. The resulting JWT is then validated by the SCIM server using the JWKS authentication method.
Keycloak prerequisites:
- Create a Keycloak client with Client Authentication enabled (confidential client).
- Enable Service Accounts Enabled on the client.
- Note the client ID and client secret.
Keycloak settings:
| Setting | Value |
|---|---|
SCIM_AUTHENTICATION_MODE |
EXTERNAL |
SCIM_EXTERNAL_ISSUER |
https://mykeycloak.example.com/realms/my-realm |
SCIM_EXTERNAL_AUDIENCE |
The client ID of the Keycloak client, or account |
SCIM_EXTERNAL_JWKS_URI |
https://mykeycloak.example.com/realms/my-realm/protocol/openid-connect/certs |
Okta setup:
- In the Okta Admin Console, go to Applications > Applications.
- Select your SCIM application.
- Go to the Provisioning tab and click Configure API Integration.
- Select OAuth2 as the authentication mode.
- Configure the following:
- Access Token Endpoint:
https://mykeycloak.example.com/realms/my-realm/protocol/openid-connect/token - Client ID: The Keycloak client ID
- Client Secret: The Keycloak client secret
- Access Token Endpoint:
- Set the SCIM connector base URL to:
https://mykeycloak.example.com/realms/my-realm/scim/v2 - Click Test API Credentials to verify.
- Click Save.
Identity Provider linking with Entra ID requires a few additional configuration steps on both the Entra and Keycloak sides.
Step 1: Add externalId
In the Keycloak admin console, ensure that you have an externalId attribute defined in your Realm Settings > User Profile. This attribute is used to store the user's external ID in Keycloak and without it the Identity Provider linking will fail.
Step 2: Map externalId in SCIM provisioning
In Entra ID, make sure that the objectId from Entra ID is mapped into the SCIM externalId field:
- Navigate to your Enterprise Application > Provisioning > Attribute Mapping (Preview) > Provision Microsoft Entra ID Users.
- Click Add New Mapping.
- Set:
- Source attribute:
objectId - Target attribute:
externalId
- Source attribute:
- Click Save.
This ensures that during SCIM provisioning, the Entra objectId is stored in Keycloak as the user's externalId, which will later be used for identity linking.
Step 3: Configure Keycloak Identity Provider to Use Object ID
Configure your Entra ID Identity Provider in Keycloak to use the oid claim from the login token instead of the default sub claim (which is app-specific).
- Navigate to Identity Providers > select your Entra ID provider.
- Go to the Mappers tab.
- Click Add Mapper.
- Fill in the mapper details:
- Name:
map_oid_as_brokerid(or any descriptive name) - Sync Mode: Force
- Mapper Type: Username Template Importer
- Template:
${CLAIM.oid} - Target: BROKER_ID
- Name:
- Click Save.
This mapper tells Keycloak to use the Entra oid claim as the Broker ID, ensuring that the login user is matched correctly with the SCIM-provisioned user.
Step 4: Enable Identity Provider Linking in SCIM
Add the following settings to your SCIM configuration:
SCIM_LINK_IDP=true
If you want to link users to a realm-level identity provider, also add:
SCIM_IDENTITY_PROVIDER_ALIAS=<your-idp-alias>
This ensures that when a user is provisioned via SCIM, a corresponding Identity Provider link is created automatically based on the externalId / oid.
By default, the SCIM server only exposes users who are explicitly assigned the scim-managed role within the realm. This ensures that only users intended to be managed through SCIM are returned or modifiable via SCIM API operations.
This prevents accidental exposure or modification of users that were:
- created manually via the Keycloak admin UI
- imported from external identity providers
- or otherwise not intended to be managed through SCIM
If you want to expose all users (i.e., bypass filtering), you can simply assign the scim-managed role to every user. This effectively disables the filter, making the SCIM behavior equivalent to an unfiltered list.
This role-based filtering applies to all SCIM operations, including:
GET /UsersPATCH /Users/{id}DELETE /Users/{id}
Users without the scim-managed role will be invisible to SCIM clients -- they won't be listed, updated, or removed through SCIM.
This filtering mechanism is designed to improve safety, especially in complex deployments involving federated users, legacy accounts, or overlapping identity sources (such as Entra ID + local users).
This design does mean that provisioning a user through SCIM who previously existed without the role may cause conflicts or provisioning failures if role assignment isn't handled correctly. However, this is a deliberate design choice to provide fine-grained control over which users are SCIM-visible.