10

I am wanting to use Keycloak to authorise access for my API.

I have got the relevant scopes defined, and these are coming back in the access token as expected:

{
  ... claims ...
  "scope": "openid profile user/Patient.write user/Patient.read",
  ... etc ...
}

but the server hosting the APIs expects scopes in an access token represented as an array, like this:

{
  ... claims ...
  "scope": [
    "openid",
    "profile",
    "user/Patient.read",
    "user/Patient.write"
  ],
  ...etc...
}

I can't see anywhere in Keycloak where I could alter the behaviour to output scopes as an array?

I've looked at doing it using a custom token mapper script, but the scopes don't appear to be available in mapper scripts, so it doesn't look like I can re-map them that way. Is there any way of getting the scopes into this form in the token in Keycloak?

1 Answers1

2

The only way I've found to solve this is to provide custom mapper as Keycloak extension.

So you can create a mapper which extends from the default AbstractOIDCProtocolMapper and override the existing scope claim inside the transformAccessToken method:

package com.example;

import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class CustomOIDCProtocolMapper extends AbstractOIDCProtocolMapper
    implements OIDCAccessTokenMapper {

    public static final String PROVIDER_ID = "oidc-custom-protocol-mapper";

    @Override
    public AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
        token.getOtherClaims().put("scope", Arrays.asList(token.getScope().split(" ")));
        setClaim(token, mappingModel, userSession, session, clientSessionCtx);
        return token;
    }

    @Override
    public String getDisplayCategory() {
        return TOKEN_MAPPER_CATEGORY;
    }

    @Override
    public String getDisplayType() {
        return "Custom Scope Claim Mapper";
    }

    @Override
    public String getHelpText() {
        return "Modifies `scope` claim in resulting access token";
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return Collections.emptyList();
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }
}

Then register your mapper by creating the resources/META-INF/services/org.keycloak.protocol.ProtocolMapper text file:

com.example.CustomOIDCProtocolMapper

Then package the extension as a Jar file and put it into your Keycloak providers/ directory. If the extension is loaded properly, you should be able to add a mapper by going to your client settings.

Adding a mapper to a client

So your client Mappers tab content should look similar to this:

Resulting list of client mappers

Once the mapper is properly added to a client, the scope claim is going to be mapped as JSON array in the resulting access token.

Documentation on Keycloak extension developent can be found here.

Andrey M.
  • 21
  • 1
  • 7
  • I tried your technique but keycloak doesn't detect the mapper. I'm not clear with the structure of the .jar file that has to be created. Could you provide a link to a valid jar file ? I'm following this structure - https://www.baeldung.com/wp-content/uploads/2023/04/directory-structure.png – Arun Jul 12 '23 at 07:55
  • @Arun Please check my GitHub repo with the working example: https://github.com/gim-/keycloak-custom-scopes-extension – Andrey M. Jul 13 '23 at 09:44