[keycloak-user] filter group claim in token per client

Dmitry Telegin dt at acutus.pro
Fri Nov 9 18:34:16 EST 2018


Ronald,

Here are some Pro Tips(tm) for you :)

- use keycloakSession.context.client.clientId to retrieve client ID (works for both tokens and userinfo);
- use Java.from() and Java.to() to convert objects and arrays from Java to JavaScript and vice versa;
- use more JavaScript-fu like map() and filter() to avoid looping over arrays;
- use RegExp for generic case-insensitive pattern matching.

With the above, your whole mapper could look as simple as this:

==========================================
/**
 * Available variables: 
 * user - the current user
 * realm - the current realm
 * token - the current token
 * userSession - the current userSession
 * keycloakSession - the current userSession
 */

var client = keycloakSession.context.client.clientId;

var groups = Java.from(user.groups)
    .map(function(group) {
        return group.name;
    })
    .filter(function(name) {
        return RegExp("(\\w+)-" + client + "-(\\w+)", "i").test(name);
    })

token.setOtherClaims("fGroup", Java.to(groups, "java.lang.String[]"))
==========================================

Please also read my earlier reply about the potential security issue with the script authenticator and how to mitigate it.

In fact, this problem (restricting access to clients based on group membership) has surfaced here at least three times during last month, so I think I'd write an article with the solution walkthrough. Stay tuned and good luck :)

Dmitry Telegin
CTO, Acutus s.r.o.
Keycloak Consulting and Training

Pod lipami street 339/52, 130 00 Prague 3, Czech Republic
+42 (022) 888-30-71
E-mail: info at acutus.pro

On Tue, 2018-11-06 at 20:51 +0000, Ronald Demneri wrote:
> I configured the client to not use the userinfo endpoint for the group mapping.  Instead I used the id token,  and everything looks good now (no errors in the log,  and the client gets the claim, and assigns permissions accordingly) . Anyhow,  the question remains,  is there a way to get the client id using the script mapper?
> 
> Thanks in advance, 
> Ronald
> 
> Sent from my HTC
> 
> ----- Reply message -----
> > From: "Ronald Demneri" <ronald.demneri at amdtia.com>
> > > To: "Ronald Demneri" <ronald.demneri at amdtia.com>, "Dmitry Telegin" <dt at acutus.pro>, "keycloak-user at lists.jboss.org" <keycloak-user at lists.jboss.org>
> Subject: [keycloak-user] filter group claim in token per client
> Date: Tue, Nov 6, 2018 16:08
> 
> Hello again,
> 
> Upon testing login and experimenting where the claim should be inserted, I found out that the duplicate print() is a result of including the claim in both ID access tokens. The error comes as a result of including the claim in the userinfo token, and probably that is why the userinfo endpoint does not contain the claim when the client application requests it.
> 
> Any idea how to solve it?
> 
> 
> Thanks in advance,
> Ronald
> 
> -----Original Message-----
> From: Ronald Demneri 
> Sent: 06.Nov.2018 12:01 PM
> > To: Ronald Demneri <ronald.demneri at amdtia.com>; Dmitry Telegin <dt at acutus.pro>; keycloak-user at lists.jboss.org
> Subject: RE: [keycloak-user] filter group claim in token per client
> 
> So, I am looking at the logs and receive the following when going to App1 > Client Scopes > Evaluate:
> 
> 2018-11-06 10:51:42,407 INFO  [stdout] (default task-1892) ############################################ APP1
> 2018-11-06 10:51:42,407 INFO  [stdout] (default task-1892) ############################################
> 2018-11-06 10:51:42,407 INFO  [stdout] (default task-1892)  We are here!!!
> 2018-11-06 10:51:42,408 INFO  [stdout] (default task-1892) ############################################
> 
> But when trying to actually log in to the client, I receive the following:
> 
> 2018-11-06 10:52:20,465 INFO  [stdout] (default task-1891) ############################################ APP1
> 2018-11-06 10:52:20,465 INFO  [stdout] (default task-1891) ############################################
> 2018-11-06 10:52:20,465 INFO  [stdout] (default task-1891)  We are here!!!
> 2018-11-06 10:52:20,466 INFO  [stdout] (default task-1891) ############################################
> 2018-11-06 10:52:20,474 INFO  [stdout] (default task-1891) ############################################ APP1
> 2018-11-06 10:52:20,474 INFO  [stdout] (default task-1891) ############################################
> 2018-11-06 10:52:20,474 INFO  [stdout] (default task-1891)  We are here!!!
> 2018-11-06 10:52:20,475 INFO  [stdout] (default task-1891) ############################################
> 2018-11-06 10:52:20,691 ERROR [org.keycloak.protocol.oidc.mappers.ScriptBasedOIDCProtocolMapper] (default task-1891) Error during execution of ProtocolMapper script: org.keycloak.scripting.ScriptExecutionException: Could not execute script 'token-mapper-script_filteredGroupsMapper' problem was: TypeError: null has no such function "toUpperCase" in <eval> at line number 31
> 
> Line 31 is as follows:
> 
> 31:    var client = token.getIssuedFor().toUpperCase();
> 32:    print("############################################ " + client);
> 
> So why does it display an error, when in fact it also displays the correct form of the clientId in upper case? And why is the log entry duplicated? ATM, I removed the client scope mapper and have recreated the script mapper only for this client.
> 
> 
> Regards,
> Ronald
> 
> 
> -----Original Message-----
> From: Ronald Demneri 
> Sent: 06.Nov.2018 11:05 AM
> > > To: 'Ronald Demneri' <ronald.demneri at amdtia.com>; 'Dmitry Telegin' <dt at acutus.pro>; 'keycloak-user at lists.jboss.org' <keycloak-user at lists.jboss.org>
> Subject: RE: [keycloak-user] filter group claim in token per client
> 
> Hello Dmitry,
> 
> A colleague of mine helped solving the issue with the array, and I can see the filtered groups in the Access token. I also used token.getIssuedFor() to get the client name and make the evaluation of the filtered groups dynamic. The problem now is that this new claim is not present in the userinfo. This is the script that we came up with (configured both as client scopes (possibly define as a default client scope) as well as script mapper specific to this client for test purposes - claim names are different of course):
> 
> > [kcadmin at keycloak bin]$ ./kcadm.sh get client-scopes [ {
>   "id" : "4ea94866-044e-4590-a2da-f25c980f08b4",
>   "name" : "Filtered_Groups",
>   "protocol" : "openid-connect",
>   "attributes" : {
>     "display.on.consent.screen" : "true"
>   },
>   "protocolMappers" : [ {
>     "id" : "7d3c521a-b291-4f43-ad87-6891ed9584d3",
>     "name" : "Filtered Groups",
>     "protocol" : "openid-connect",
>     "protocolMapper" : "oidc-script-based-protocol-mapper",
>     "consentRequired" : false,
>     "config" : {
>       "multivalued" : "true",
>       "userinfo.token.claim" : "true",
>       "id.token.claim" : "true",
>       "access.token.claim" : "true",
>       "claim.name" : "fGroup",
>       "jsonType.label" : "String",
>       "script" : "/**
>         * Available variables:
>         * user - the current user
>         * realm - the current realm
>         * token - the current token
>         * userSession - the current userSession
>         * keycloakSession - the current userSession
>         */
>         
>         //insert your code here...
> 
>         //So, first we need to know, how many names should be added to the new claim,
>         var username = user ? user.username : \"anonymous\";
>         var groups = user.getGroups();
>         var group_array = groups.toArray();
>         //print(\"########################################## \" + username);
> 
>         var client = token.getIssuedFor();
>         //print(\"############################################ \" + client);
> 
>         var clUp = client.toUpperCase();
>         //print(clUp);
> 
>         var group_APP = \"APP-\" + clUp + \"-USERS\";
>         var group_ROL = \"ROL_SSO-\" + clUp + \"-ADMIN\";
> 
>         var group_filtered = [];
> 
>         for (var i in group_array) {
>                 var gn = group_array[i].getName();
>                 var gnUp = gn.toUpperCase();
>                 if (gnUp === group_APP || gnUp === group_ROL) {
>                         group_filtered.push(\"/\" + gn);
>                         }
>                 }
>         //Then we declare the new array.
>         var l = group_filtered.length;
>         var group_token = java.lang.reflect.Array.newInstance(java.lang.String.class, l);
> 
>         for (var f in group_filtered) {
>                 group_token[f] = group_filtered[f];
>                 //print(group_token[f]);
>         }
> 
>         //And submit the array as token
>         token.setOtherClaims(\"fGroup\", group_token);"
>     }
>   } ]
> }
> 
> This is the userinfo data for my account:
> 
> {
>   "sub": "bad7ff26-2a70-446f-a635-06fdbe1bec55",
>   "Group": [
>     "/APP-App1-Users/TGR-Team-ABC",
>     "/APP-App1-Users/TGR-Team-DEF",
>     "/APP-App1-Users",
>     "/APP-MySmallApp-Users"
>   ],
>   "email_verified": false,
>   "name": "Ronald Demneri",
>   "preferred_username": "u151302",
>   "given_name": "Ronald",
>   "family_name": "Demneri"
> 
> 
> The group claim is inserted by the group mapper created for this client, and the idea is to remove it once the script mapper works as expected.
> What do you think is going on? Is this behavior normal?
> 
> Thanks in advance,
> Ronald
> 
> -----Original Message-----
> From: Ronald Demneri
> Sent: 05.Nov.2018 12:12 PM
> > To: 'Ronald Demneri' <ronald.demneri at amdtia.com>; Dmitry Telegin <dt at acutus.pro>; keycloak-user at lists.jboss.org
> Subject: RE: [keycloak-user] filter group claim in token per client
> 
> Hello,
> 
> In the script authenticator there was authenticationSession which I used to get the clientId. There is no such variable in the script mapper, and If I define such mapper in the client template, I suppose I'd need some mechanism to get the client name and then make the filtering of the groups that need to be inserted in the token. How do I do that? Is there any documentation available for this online?
> 
> 
> Thanks again for your support!
> Ronald
> 
> 
> -----Original Message-----
> > From: keycloak-user-bounces at lists.jboss.org <keycloak-user-bounces at lists.jboss.org> On Behalf Of Ronald Demneri
> Sent: 05.Nov.2018 11:00 AM
> > To: Dmitry Telegin <dt at acutus.pro>; keycloak-user at lists.jboss.org
> Subject: Re: [keycloak-user] filter group claim in token per client
> 
> Hello Dmitry,
> 
> Thanks for the response. In fact I tried that before posting here, created a custom script mapper for the client that I have configured. The problem is that the script will return a list of objects, not an array of strings, which is what I am expecting.
> 
> What do I need to pay extra attention in order to solve this?
> 
> 
> Thanks in advance and Regards,
> Ronald
> 
> -----Original Message-----
> > From: Dmitry Telegin <dt at acutus.pro>
> Sent: 05.Nov.2018 6:54 AM
> > To: Ronald Demneri <ronald.demneri at amdtia.com>; keycloak-user at lists.jboss.org
> Subject: Re: [keycloak-user] filter group claim in token per client
> 
> Hello Ronald,
> 
> As in the case with authentication, JavaScript is to the rescue again :) You can create a script mapper for groups that will do additional group filtering based on the client, and use it instead of the built-in one.
> 
> To avoid explicitly configuring it for each and every client, you can create a Client Scope (can be called "Client Template" depending on the KC version), define the mapper in the scope, and add it do default scopes.
> 
> Cheers,
> Dmitry Telegin
> CTO, Acutus s.r.o.
> Keycloak Consulting and Training
> 
> Pod lipami street 339/52, 130 00 Prague 3, Czech Republic
> +42 (022) 888-30-71
> > E-mail: info at acutus.pro 
> 
> On Fri, 2018-11-02 at 10:30 +0000, Ronald Demneri wrote:
> > Hello everyone,
>> > Is there a way to filter the groups a user is a member of per client, based on clientId (which is part of the group name(s) in AD). Let's say that user Ronald is member of  group_client1, group_client2 and group_client3, so using a group mapper, the token will contain a claim like group:["group_client1", "group_client2", "group_client3"]. Upon logging in to client1 app, I want to customize the group claim so that it contains only the respective group_client1 value.
>> > Thanks in advance,
>> > Ronald
> > _______________________________________________
> > keycloak-user mailing list
> > keycloak-user at lists.jboss.org
> > https://lists.jboss.org/mailman/listinfo/keycloak-user
> 
> _______________________________________________
> keycloak-user mailing list
> keycloak-user at lists.jboss.org
> https://lists.jboss.org/mailman/listinfo/keycloak-user


More information about the keycloak-user mailing list