Keycloak SCIM Authentication and Permissions

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package Keycloak 26.7.3 (ZIP, start-dev --http-port=18082 --features=scim-api)
curl-8.12.1-4.el10_2.4.x86_64
jq-1.7.1-11.el10_2.2.x86_64
Applies to Keycloak 26.7.x SCIM authentication (Preview, --features=scim-api). On Keycloak 26.8+, SCIM is supported and the authentication model is the same, but current Keycloak guidance requires Full Scope Allowed to be disabled and the required realm-management roles to be assigned both to the client's dedicated scope and its service account.
Lab environment Isolated Keycloak on http://127.0.0.1:18082 (separate from other labs on port 8080). Enable SCIM on the realm first — Keycloak SCIM API.
Privilege Bootstrap admin to create clients, mappers, and service-account roles
Scope How a non-human SCIM client authenticates to Keycloak (OAuth client credentials and bearer tokens), why the access token must list the SCIM base URL in aud, and which realm-management roles allow discovery, read, search, and write. Does not cover first-time SCIM enablement, user/group CRUD walkthroughs, or Entra provisioning.
Related guides Keycloak SCIM API
Fix Keycloak SCIM 401 and 403
Manage users and groups with SCIM
Keycloak service accounts
Keycloak client credentials
Short on time?Beta Get the gist of this 15-minute read in seconds, then ask follow-up questions

This article covers Keycloak's built-in SCIM API (26.7 preview, 26.8 supported). It is not about older third-party Keycloak SCIM extensions.

A SCIM connector does not log in like a person. Instead, it authenticates as an application using a client ID and client secret. Keycloak returns an OAuth access token, and the connector sends that token with each SCIM request. Keycloak then verifies the token, checks that the token was issued for this SCIM API (the audience), and checks whether the client's service account has permission to perform the requested operation.

This guide builds that setup step by step on Keycloak 26.7.3. It uses two example clients (scim-reader and scim-provisioner) so you can see exactly how read-only and provisioning permissions differ. Keycloak does not require two clients — one confidential client with the right roles is enough. Enable the server feature and realm SCIM switch first; the steps below assume realm golinuxcloud already exposes /realms/golinuxcloud/scim/v2.

Quick answer

To call Keycloak's SCIM API, create a confidential client with Service account roles enabled and obtain an access token with the client credentials grant. The token must contain the realm's SCIM base URL in its aud claim. Give the service account view-users for read-only access, or manage-users when the connector must create, update, or delete users and groups.

Client ID + client secret
        |
        v
OAuth client_credentials
        |
        v
   Access token
        |
        v
SCIM URL in aud? ----no----> 401
        |
       yes
        v
Required realm-management role? ----no----> 403
        |
       yes
        v
   SCIM request allowed
IMPORTANT

Keycloak 26.7.x: SCIM is a Preview feature. Start the server with --features=scim-api.

Keycloak 26.8 and later: SCIM is a supported feature and the scim-api feature is enabled by default.

Bearer authentication, audience validation, and realm-management permissions apply on both paths. On 26.8+, Keycloak instructs you not to enable Full Scope Allowed on the SCIM client — set it to Off on the dedicated scope, assign the required realm-management roles under Scope mappings on that dedicated scope, and assign the same roles on Service account roles. The access token receives the intersection of those two role lists. The screenshots in this guide were captured on Keycloak 26.7.3. The dedicated-scope configuration shown below follows the current Keycloak 26.8 guidance. In the 26.7.3 lab, assigning the role only under Service account roles was sufficient; on 26.8+, configure both the dedicated-scope role mapping and the service-account role assignment.


How Keycloak SCIM authentication works

Keycloak SCIM uses OAuth 2.0 bearer tokens (RFC 6750). There is no separate SCIM username or password and no API key header defined by Keycloak itself. Only confidential clients can access the SCIM endpoints.

Three things decide whether the SCIM request succeeds:

  1. Client authentication — the client ID and secret authenticate the SCIM application to Keycloak's token endpoint.
  2. Token audience — the resulting access token must identify the SCIM API as an intended recipient (aud includes the realm SCIM base URL).
  3. Authorization — the service account's realm-management roles (and on 26.8+, the intersection with dedicated-scope role mappings) determine what the caller can do.

Authentication (is the token valid and intended for this SCIM API?) is separate from authorization (is this service account allowed to perform the operation?).

Think of the HTTP status codes this way:

  • 401 Unauthorized — Keycloak cannot accept this token for the SCIM API (missing bearer token, invalid JWT, or wrong audience).
  • 403 Forbidden — the token is accepted, but the caller is not allowed to perform this operation (missing role, or a protected administrative user or group).

Symptom-first fixes live on Fix Keycloak SCIM 401 and 403.

SCIM provisions accounts; it does not replace OIDC or SAML login — see LDAP vs SAML vs OIDC vs SCIM. For vocabulary around service provider versus identity provider, see IdP vs service provider.


Lab layout

This article uses a dedicated Keycloak process on port 18082 so it does not touch another lab on 8080.

Item Value
SCIM base URL http://127.0.0.1:18082/realms/golinuxcloud/scim/v2
Token endpoint http://127.0.0.1:18082/realms/golinuxcloud/protocol/openid-connect/token
Example read client scim-reader / <reader-client-secret> with view-users
Example provision client scim-provisioner / <provisioner-client-secret> with manage-users
Sample user ada (local realm user for read tests)

Export the SCIM base URL once in your shell. Every curl example appends /Users, /Groups, or /ServiceProviderConfig to it:

bash
SCIM_BASE='http://127.0.0.1:18082/realms/golinuxcloud/scim/v2'

Create a confidential SCIM client

A SCIM connector is a server-side application, not a person signing in through a browser. In Keycloak, create it as a confidential client by turning Client authentication on. That lets the application authenticate with its own client credentials. Enable Service account roles so those credentials represent a non-human Keycloak service account.

Public clients are for applications that cannot safely keep a client secret, such as browser or mobile apps. Keycloak does not support them for SCIM API calls.

Open http://127.0.0.1:18082/admin/, sign in as the bootstrap admin, and select realm golinuxcloud. Create the read example client first:

  1. Open Clients and click Create client.
  2. Set Client ID to scim-reader and click Next.
  3. On Capability config, switch Client authentication to On.
  4. Under Authentication flow, clear Standard flow and enable Service account roles only.
  5. Click Next, leave login URLs empty, and click Save.

Repeat the wizard for scim-provisioner if you want a separate provision example. One client is enough for production when you assign the roles the connector needs.

When Service account roles is enabled, Keycloak creates an internal user for that client. For scim-reader, the username is service-account-scim-reader. Roles on the Service account roles tab belong to this non-human identity, not to an employee account.

The Capability config section should show client authentication on and only service account roles enabled:

Keycloak scim-reader Capability config with Client authentication On, Standard flow Off, and Service account roles On

After Save, open Credentials on each client. Copy the generated secret from behind the eye icon into your connector vault — not into screenshots or tickets:

Keycloak scim-reader Credentials tab showing the masked Client secret field and reveal control

Treat the client secret like a password. Anyone who obtains it can authenticate as this service account and receive whatever permissions its access token allows.


Request a SCIM access token with client credentials

Keycloak issues tokens through the client credentials grant. The client_credentials grant returns an access token for the service account. The connector requests another access token when the current one expires; there is normally no user-style refresh-token workflow here.

Copy the generated secret from the Credentials tab, then export it in your shell:

bash
READER_SECRET='paste-generated-secret-here'

Request a token for the read client:

bash
curl -sS -X POST 'http://127.0.0.1:18082/realms/golinuxcloud/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=scim-reader' \
  -d "client_secret=$READER_SECRET"
output
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ...",
  "expires_in": 300,
  "token_type": "Bearer",
  "scope": "profile email"
}

expires_in is the lifetime in seconds for this realm and client. Request a new token after role or mapper changes; existing JWTs keep their old claims.

Store only the access token when you script SCIM calls:

bash
READER_TOKEN=$(curl -sS -X POST 'http://127.0.0.1:18082/realms/golinuxcloud/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=scim-reader' \
  -d "client_secret=$READER_SECRET" | jq -r '.access_token')

The assignment prints nothing. Use the same pattern with scim-provisioner and its secret when you test writes.


Add the SCIM audience to the Keycloak access token

aud is short for audience. It tells the receiving API which service the token was intended for. A token can be correctly signed by Keycloak and still be rejected by SCIM if the SCIM base URL is not one of its audiences.

A valid client-credentials token is not automatically valid for SCIM. Keycloak checks that the JWT aud claim includes the exact SCIM base URL for the realm — scheme, host, port, and /realms/<realm>/scim/v2 path. Without that audience, SCIM returns 401 with Invalid token audience even when view-users is assigned. That is authentication, not a missing role.

Add an Audience protocol mapper on each client's dedicated scope:

  1. Open Clients → scim-reader → Client scopes.
  2. Click scim-reader-dedicated in the assigned list.
  3. Click Configure a new mapper → Audience (or open the existing scim-audience mapper).
  4. Set Name to scim-audience.
  5. Leave Included Client Audience empty. Set Included Custom Audience to http://127.0.0.1:18082/realms/golinuxcloud/scim/v2.
  6. Enable Add to access token and click Save.

Repeat on scim-provisioner-dedicated for the provisioner client.

Keycloak Audience mapper scim-audience with Included Custom Audience set to the golinuxcloud SCIM base URL and Add to access token On

Behind a reverse proxy, configure the audience with the externally visible SCIM base URL expected by Keycloak, rather than blindly copying an internal container hostname. The audience comparison is URL-sensitive, so 127.0.0.1, localhost, different ports, and different schemes are not interchangeable.

You can decode the token to verify that the mapper actually added the SCIM audience. The jq pipeline below handles base64url padding that a plain base64 -d pipe often misses:

bash
curl -sS -X POST 'http://127.0.0.1:18082/realms/golinuxcloud/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=scim-reader' \
  -d "client_secret=$READER_SECRET" \
  | jq -r '.access_token | split(".")[1] | gsub("-";"+") | gsub("_";"/") | . + "==" | @base64d | fromjson | {azp, aud}'
output
{
  "azp": "scim-reader",
  "aud": [
    "http://127.0.0.1:18082/realms/golinuxcloud/scim/v2",
    "realm-management",
    "account"
  ]
}

The SCIM URL appears in aud alongside built-in audiences. azp identifies the client that requested the token.


Which Keycloak roles are required for SCIM?

SCIM defines API operations, but it does not introduce a new Keycloak role such as scim-admin. Keycloak authorizes SCIM requests using existing administrative roles on the built-in realm-management client. For example, view-users allows reading user resources, while manage-users permits create, update, and delete operations on users and groups.

On the tested Keycloak 26.7.3 setup, assign these permissions under Clients → <client> → Service account roles, not under the client's own Roles tab. Keycloak 26.8+ additionally requires the corresponding role scope mappings described below.

Read-only access with view-users

For a read-only connector — one that periodically reads users and group membership from Keycloak but cannot create, modify, or delete them:

  1. Open Clients → scim-reader → Service account roles.
  2. Click Assign role → Filter by clients → realm-management.
  3. Check view-users and click Assign.

In this Keycloak 26.7.3 lab, assigning view-users also brought the query permissions needed for discovery. If you configure narrower permissions manually, remember that user searches require query-users, group searches require query-groups, and discovery accepts either one.

Keycloak scim-reader Service account roles with realm-management view-users assigned

Provisioning access with manage-users

For connectors that create, update, patch, or delete users and groups, assign manage-users on the provisioner client:

Keycloak scim-provisioner Service account roles with realm-management manage-users assigned

manage-users covers write operations for both users and groups in this setup. You do not need a separate group-management role for standard SCIM provisioning.

If Fine-Grained Admin Permissions are enabled, these broad roles can be restricted further so the SCIM client can access only selected users or groups.

Keycloak 26.8 and later: dedicated scope and Full Scope Allowed

On Keycloak 26.8+, do not enable Full Scope Allowed on the SCIM client. Set Full Scope Allowed to Off on the dedicated scope (scim-reader-dedicated, and so on). Assign the same realm-management roles under Clients → <client> → Client scopes → <client>-dedicated → Scope → Assign role, and on Service account roles. The access token receives the intersection of those two role lists.

  1. Open Clients → scim-reader → Client scopes → scim-reader-dedicated.
  2. Open the Scope tab and confirm Full scope allowed is Off.
  3. Click Assign role → Client roles → filter to realm-management → assign view-users (or manage-users on a provisioner client).
  4. Assign the same role on Service account roles as in the sections above.

Keycloak scim-reader-dedicated Scope tab with Full scope allowed Off and realm-management view-users on scope mappings

If a role appears only on Service account roles but not on the dedicated scope mappings, the token omits it when Full Scope Allowed is off. On 26.7.3 in this lab, service-account roles alone were enough; on 26.8+, configure both places before you test tokens.


Keycloak SCIM role matrix

These are the roles Keycloak checks for each SCIM operation. The Observed result column reflects what I saw on Keycloak 26.7.3 with SCIM enabled on realm golinuxcloud.

SCIM request Minimum realm-management role Observed result in this lab without it
Discovery (/ServiceProviderConfig, /ResourceTypes, /Schemas) query-users or query-groups 403 with no detail
Search or list /Users query-users 403 or empty Resources
Search or list /Groups query-groups 403 or empty Resources
Read /Users/{id} view-users 403
Read /Groups/{id} view-users 403
Create, update, or delete users or groups manage-users 403

query-users and view-users are not the same. query-users permits searching and listing users; view-users permits returning complete user resources. In my 26.7.3 test, a token with only query-users could report totalResults while returning no entries in Resources. Most read-only SCIM connectors should use view-users rather than assembling only query permissions.

view-realm alone did not unlock SCIM discovery in this lab.

With scim-reader and view-users, reads succeed but POST /Users returns 403 until you assign manage-users on a provisioner client and request a new token.


Verify the configuration

First test a discovery endpoint with the reader token:

bash
curl -sS -w '\nHTTP %{http_code}\n' -H "Authorization: Bearer $READER_TOKEN" \
  -H 'Accept: application/scim+json' "$SCIM_BASE/ServiceProviderConfig" | tail -3
output
... "authenticationSchemes":[{"type":"oauthbearertoken","name":"OAuth Bearer Token"...
HTTP 200

HTTP 200 on ServiceProviderConfig confirms bearer authentication, audience, and at least query-level permission.

List users to confirm read access to resource bodies:

bash
curl -sS -H "Authorization: Bearer $READER_TOKEN" -H 'Accept: application/scim+json' \
  "$SCIM_BASE/Users?count=10" | jq '{totalResults, Resources: [.Resources[]?.userName]}'
output
{
  "totalResults": 1,
  "Resources": [
    "ada"
  ]
}

User ada appears in the list. Service account users created for clients do not show up as ordinary SCIM users.


Optional: separate read-only and provisioning clients

Keycloak requires only one SCIM client, but you may deliberately separate read-only and provisioning integrations:

  • scim-reader + view-users: reads users and groups without write permission.
  • scim-provisioner + manage-users: creates, modifies, and deletes users and groups.

Separate credentials reduce the permissions exposed if the read-only connector's secret is compromised. Do not assign realm-admin or manage-realm to SCIM service accounts unless you intend to grant broad admin API access through the same token.


SCIM tokens vs Admin REST API tokens

Both APIs live on the same Keycloak realm, but they are not interchangeable. SCIM relies on the same administrative permission model as the Admin REST API, but SCIM adds its own endpoint and audience requirements.

Topic SCIM (/realms/<realm>/scim/v2) Admin REST (/admin/realms/<realm>/...)
Endpoint /realms/<realm>/scim/v2 /admin/realms/<realm>/...
Access token Must satisfy SCIM audience validation Must carry permissions accepted by Admin REST
Authorization realm-management roles (and FGAP when enabled) realm-management roles (and FGAP when enabled)
Primary use Standards-based identity provisioning Keycloak-specific administration

A token that works against /admin/realms/.../users is not automatically accepted by SCIM. Configure SCIM audience and roles on the connector client, then test against /scim/v2 endpoints.


Troubleshooting 401 and 403

Symptom Likely cause Fix
401 Bearer token required Missing or malformed Authorization header Send Authorization: Bearer <access_token> from client credentials
401 Invalid token audience aud lacks SCIM base URL Add Audience mapper with exact SCIM URL; request a new token
403 on discovery No query-users / query-groups Assign query role or view-users
403 on GET /Users/{id} Only query-users Assign view-users
403 on POST /Users Only view-users Assign manage-users on provisioner client
403 with roles assigned (26.8+) Role only on service account, not on dedicated scope Disable Full Scope Allowed, then assign the required role to both the dedicated client's Scope mappings and Service account roles
403 on admin user write Protected administrative account Change admin outside SCIM

Step-by-step reproductions and curl transcripts for each status code are in the companion troubleshooting article for SCIM 401 and 403 responses.


Summary

Keycloak SCIM authentication is OAuth bearer tokens from confidential service-account clients. The client credentials grant supplies the JWT; an Audience mapper adds the realm SCIM base URL to aud; realm-management roles on the service account decide whether discovery, read, or write succeeds. On Keycloak 26.8+, keep Full Scope Allowed off and assign the same roles on the dedicated scope and the service account so the token receives their intersection.

In this lab, scim-reader with view-users could list and read user ada but could not create users. scim-provisioner with manage-users could create accounts once the same audience mapper was in place. The access token lifetime here was five minutes (expires_in: 300), and role changes required a fresh token.

When you configure a real SCIM connector, enable SCIM on the realm first, then configure the audience and only the roles the connector actually needs before you point Entra or another system at the SCIM URL. If a request still returns 401 or 403 after you assign the expected role, check token and audience problems before changing roles.


References


Frequently Asked Questions

Does Keycloak SCIM use a bearer token?

Yes. Every SCIM request must send Authorization: Bearer <access_token>. Keycloak documents OAuth bearer authentication in ServiceProviderConfig. Obtain the token with the OAuth 2.0 client credentials grant on a confidential client that has service accounts enabled.

Why does Keycloak SCIM require an audience claim?

The access token must name the SCIM API as an intended recipient. Keycloak compares the request URL to the JWT aud claim. If the realm SCIM base URL is missing, SCIM returns 401 Invalid token audience before it evaluates realm-management roles.

Which role is required to create SCIM users?

Assign the realm-management role manage-users to the client's service account and to the client's dedicated scope role mappings as well. Keycloak 26.8 documentation instructs SCIM clients to use Full Scope Allowed = Off. view-users allows reads; it does not authorize POST, PUT, PATCH, or DELETE on /Users or /Groups.

Can a public Keycloak client call SCIM?

No. Keycloak expects a confidential client that can authenticate with a client secret and use the client credentials grant. Public clients are not appropriate for server-side provisioning secrets.

Should I use an admin user token for SCIM?

No. Use a dedicated confidential client with service accounts and only the realm-management roles the connector needs. Admin user passwords and broad realm-admin tokens expand blast radius if the connector or secret leaks.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)