Service accounts in Keycloak don’t authenticate in the traditional sense; they authorize by acting as a client identity that can request tokens on behalf of a system, not a user.
Let’s see this in action. Imagine a backend service, my-backend-service, that needs to call another protected API, protected-api. We’ll set up my-backend-service as a "confidential" client in Keycloak.
First, in Keycloak, create a new client.
- Client ID:
my-backend-service - Client Protocol:
openid-connect - Access Type:
confidential
This confidential type is crucial. It means the client (our service) has a secret that it uses to authenticate itself to Keycloak’s token endpoint. Keycloak will generate a client_secret for this client. Copy this secret; we’ll need it.
Now, my-backend-service needs to get an access token from Keycloak’s token endpoint. This is done using the Client Credentials Grant flow. The service will make a POST request to Keycloak’s token endpoint, typically http://your-keycloak-host:8080/realms/your-realm/protocol/openid-connect/token.
The request body will look like this (using curl for demonstration):
curl -X POST \
http://your-keycloak-host:8080/realms/your-realm/protocol/openid-connect/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&client_id=my-backend-service&client_secret=YOUR_COPIED_CLIENT_SECRET'
Here’s what’s happening:
grant_type=client_credentials: This tells Keycloak we’re using the client credentials flow.client_id=my-backend-service: This is the identifier for our service client.client_secret=YOUR_COPIED_CLIENT_SECRET: This is the secret Keycloak issued when we created theconfidentialclient.
If successful, Keycloak will respond with a JSON object containing an access_token:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 300,
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "...",
"scope": "..."
}
This access_token is what my-backend-service will use to authenticate itself to protected-api. It’s a JWT, and protected-api can validate it by checking its signature against Keycloak’s public keys and by inspecting its claims (like iss for issuer, aud for audience, and scope to ensure the service has permission for the requested operation).
The real power of service accounts lies in how you manage their permissions. You don’t assign roles to a service account like you would a user. Instead, you grant Client Roles directly to the service account client.
In Keycloak, navigate to your my-backend-service client, then go to the "Roles" tab. Here, you can define specific roles like read-data or process-orders. These are client-specific roles. You then assign these roles to the my-backend-service client itself.
When my-backend-service requests a token, it can optionally request these specific roles in the token by including scope=read-data%20process-orders in the token request. The resulting access_token will then contain a client_roles claim listing the assigned roles, allowing protected-api to authorize the request based on those specific, granular permissions.
The client_secret is the linchpin of this entire flow. If it’s compromised, an attacker can impersonate your service, obtain tokens, and access protected resources as if they were your service. Treat it with the same security considerations as any other sensitive credential, such as API keys or passwords. Store it securely in your service’s configuration, not hardcoded in source control.
The next step after successfully obtaining and using service account tokens is often managing their lifecycle, particularly refreshing them before they expire, which leads into understanding token expiration and renewal strategies.