Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keycloak add extra claims from database / external source with custom protocol mapper

I've seen those two post that give a solution to this question but they do not provide detailed enough informations about how to do it for non Java developer like me:

Keycloak add extra claims from database / external source

How to register a custom ProtocolMapper in Keycloak?

Here is a recap of their solutions that could help others if filled with more details.

Process expected from 1st link

  1. User logs in
  2. My custom protocol mapper gets called, where I overwrite the transformAccessToken method
  3. Here I log in the client where the protocol mapper is in into keycloak, as a service. Here don't forget to use another client ID instead the one you're building the protocol mapper for, you'll enter an endless recursion otherwise.
  4. I get the access token into the protocol mapper and I call the rest endpoint of my application to grab the extra claims, which is secured.
  5. Get the info returned by the endpoint and add it as extra claims

Steps to achieve it from 2nd link

Implement the ProtocolMapper interface and add the file "META-INF/services/org.keycloak.protocol.ProtocolMapper" containing the reference to the class.

At this point Keycloak recognizes the new implementation. And you should be able to configure it via the admin console.

To add some data to the token add the following interfaces

org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper

and implement the methods according to the interface

Then add the file "META-INF/jboss-deployment-structure.xml" with the following content

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.keycloak.keycloak-services"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

And after doing all this the custom transformAccessToken() method is called on every request to URL http://:/auth/realms/testrealm/protocol/openid-connect/token

After reading this I have a few questions :

  1. How do you ”Implement the ProtocolMapper”
  2. Where do you add the files mentionned earlier ? ( can't see any META-INF/ directory in my Keycloak installation folder )
  3. How and where do you ”add the following interfaces”
  4. What does the custom transformAccessToken() looks like

Thank you all for your time. Let me know if I miss summarise their answers.

Edit :

I'm starting a bounty with the hope that someone will be able to give me detailled steps on how to add extra claims from database in Keycloak 3.4.3 ( Detailed enough for a non Java dev )

Edit 2 A method descibed here could do the trick but lack details. Keycloak create a custom identity provider mapper

like image 358
AlaricB Avatar asked Oct 31 '18 18:10

AlaricB


People also ask

How do I add a custom claim to a keycloak?

In your realm, select your client. For that client, go the 'Mappers' option and then click on 'Create'. You can have the mapper type as 'User Attribute' and select the option(s) to add the attribute to ID token, access token and userinfo. The attribute added here should exist on the user.

What is protocol mapper in keycloak?

User property protocol mappers allow you to map built in properties defined on the Keycloak user interface to a claim in a token.

How do I get JWT token from keycloak?

GET AccessTokenWe send a POST request to the token endpoint: http://localhost:8090/auth/realms/wstutorial/protocol/openid-connect/token. We use openid-connect protocol which is an authentication layer on top of OAuth 2.0. Within the POST request we send data as name=value pairs separated with &


2 Answers

I hope this step by step guide helps you

I'm using Keycloak 4.5.0 - because I have this newer version installed - but I should not make a big difference. And I implemented a OIDCProtocolMapper in the example.

Just to summarize it - for the quick overview for others - each step is described more detailed later

  1. You implement a CustomProtocolMapper class based on AbstractOIDCProtocolMapper

  2. META-INF/services File with the name org.keycloak.protocol.ProtocolMapper must be available and contains the name of your mapper

  3. jboss-deployment-structure.xml need to be available to use keycloak built in classes

  4. Jar File is deployed in /opt/jboss/keycloak/standalone/deployments/

Okay now more details :-)

Create your custom Mapper

I uploaded you my maven pom.xml (pom) - just import it into your IDE and all the dependencies should be loaded automatically. The dependencies are just provided and will be later used from keycloak directly at runtime

Relevant is the keycloak.version property - all keycloak dependencies are currently loaded in version 4.5.0.Final

Now i created a custom Protocol Mapper Class called CustomOIDCProtocolMapper. Find "full" code here

It should extend AbstractOIDCProtocolMapper and need to implement all abstract methods. Maybe you want to have a SAML Protocol Mapper then it's another base class (AbstractSAMLProtocolMapper)

one relevant method is transformAccessToken - here I set a additional Claim to the AccessToken. You need your logic here but yeah - depends on your database, etc. ;-)

Services File

The services File is important for keycloak to find your custom-Implementation

Place a file with the fileName org.keycloak.protocol.ProtocolMapper inside \src\main\resources\META-INF\services\

Inside this file you write to Name of your custom Provider - so keycloak knows that this class is available as Protocol Mapper
In my example the file content is just one line

com.stackoverflow.keycloak.custom.CustomOIDCProtocolMapper

Deployment Structure XML

In your custom mapper you use files from keycloak. In order to use them we need to inform jboss about this dependency. Therefore create a file jboss-deployment-structure.xml inside \src\main\resources\META-INF\ Content:

<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.keycloak.keycloak-services" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>

Build and deploy your Extension

Build a jar File of your Extension (mvn clean package) - and place the jar in /opt/jboss/keycloak/standalone/deployments/ and restart keycloak

In the logfile you should see when it's deployed and (hopefully no) error messages

Now you can use your mapper - In my example I can create a Mapper in keycloak admin ui and select Stackoverflow Custom Protocol Mapper from dropdown

Just as info - this is not fully official supported by keycloak - so interfaces could possible change in later versions

I hope it's understandable and you will be able to succesfully implement your own mapper

EDIT: Exported eclipse file structure zip

like image 189
Evil_skunk Avatar answered Oct 25 '22 20:10

Evil_skunk


I am using a Custom Protocol Mapper1 to send an authenticated2GraphQL query3 to an external system and put the JSON response data into the user's access token (JWT). It currently runs with Keycloak 10.

==> You can find the full code in this repository.

(1) Custom Protocol Mapper

As others have noted, your project needs at least 3 files.

  1. A Java class that implements AbstractOIDCProtocolMapper & its method setClaim (among others).
  2. A jboss-deployment-structure.xml file that contains the dependencies for deployment.
  3. An org.keycloak.protocol.ProtocolMapper file that contains the full name of the custom protocol mapper.

Here is the folder structure:

$ tree src/ -A
src/
└── main
    ├── java
    │   └── com
    │       └── thohol
    │           └── keycloak
    │               └── JsonGraphQlRemoteClaim.java
    └── resources
        └── META-INF
            ├── jboss-deployment-structure.xml
            └── services
                └── org.keycloak.protocol.ProtocolMapper

(2) Authenticated Remote Requests

If the remote endpoint requires authentication, we can obtain an Access Token from Keycloak. The complete flow would look as follows (especially steps 3-6):

  1. A user sends an authentication request (i.e., "logs in") to Keycloak. The request is made against a specific Keycloak client, e.g., login-client.
  2. Because the login-client is configured to use the Custom Protocol Mapper, its code gets executed while the user's authentication request is being processed.
  3. The Custom Protocol Mapper sends a second authentication request to Keycloak. The request is made against a second Keycloak client (e.g., remote-claims-client) using client_credentials (Client ID + Secret).
  4. The Custom Protocol Mapper receives an access token for client remote-claims-client.
  5. The Custom Protocol Mapper sends a request to the remote endpoint. An Authorization: Bearer <access token> header is added to the request headers.
  6. The remote endpoint receives the request and validates the JWT token. In many cases, access should be restricted further. For example, to only allow tokens minted ("written") for the corresponding remote-claims-client.
  7. The remote endpoint returns the custom remote claims data.
  8. The Custom Protocol Mapper receives the custom remote claims data and puts it into the user's access token.
  9. Keycloak returns an access token with custom claims to the user.

Steps 3/4 can be implemented as a simple HTTP POST request in Java (error handling omitted!):

// Call remote service
HttpClient httpClient = HttpClient.newHttpClient();
URIBuilder uriBuilder = new URIBuilder(keycloakAuthUrl);
URI uri = uriBuilder.build();

HttpRequest.Builder builder = HttpRequest.newBuilder().uri(uri);
String queryBody = "grant_type=client_credentials&client_id=remote-claims-client&client_secret=dfebc62a-e8d7-4ab3-9196-258ddb5684ab";
builder.POST(HttpRequest.BodyPublishers.ofString(queryBody));

// Build headers
builder.header(HttpHeaders.CONTENT_TYPE , MediaType.APPLICATION_FORM_URLENCODED);

// Call
HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());

// Process Response
JsonNode json = return new ObjectMapper().readTree(response.body());
String accessToken = json.findValue("access_token").asText();

(3) Using GraphQL Queries for external requests

A GraphQL query is essentially an HTTP POST request, with a body like

{
    "query": "query HeroName($episode: Episode) {
        hero(episode: $episode) {
            name
        }
    }",
    "variables": {
        "episode" : "JEDI"
    }
}

This can be sent from Java like (error handling omitted!):

HttpClient httpClient = HttpClient.newHttpClient();
URIBuilder uriBuilder = new URIBuilder(remoteUrl);
URI uri = uriBuilder.build();

HttpRequest.Builder builder = HttpRequest.newBuilder().uri(uri);
String queryBody = "{
    \"query\": \"query HeroName($episode: Episode) {
        hero(episode: $episode) {
            name
        }
    }\",
    \"variables\": {
        \"episode\" : \"JEDI\"
    }
}";
builder.POST(HttpRequest.BodyPublishers.ofString(queryBody));

// Build headers
builder.header(HttpHeaders.CONTENT_TYPE , MediaType.APPLICATION_JSON);
builder.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);

// Call
HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());

// Process Response and add to token
JsonNode json = return new ObjectMapper().readTree(response.body());
clientSessionCtx.setAttribute("custom_claims", json);

Disclaimer

I am the owner/author of the linked repository. However, I did not start from scratch but used multiple other repositories as basis/inspiration. See the repo's README.

like image 35
Thomas Avatar answered Oct 25 '22 20:10

Thomas