16

I've extracted a user's groups information from the OIDC endpoint of Keycloak, but they don't come with the group ATTRIBUTES I defined (see Attributes tab into the group form, near Settings). Is there a claim to add to my request?

I'm using a RESTeasy client to reach Keycloak's admin API (had much better results than using the provided admin client, yet):

@Path("/admin/realms/{realm}")
public interface KeycloakAdminService {
    @GET
    @Path("/users/{id}/groups")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    List<GroupRepresentation> getUserGroups(@PathParam("realm") String realm, @PathParam("id") String userId,
                                            @HeaderParam(AUTHORIZATION) String accessToken);
    //DEBUG the access token must always be prefixed by "Bearer "
}

So I can fetch a user's groups:

private void fetchUserGroups(UserInfoOIDC infos, String userId) {
    log.info("Fetching user groups from {}...", getRealm());
    try {
        KeycloakAdminService proxy = kcTarget.proxy(KeycloakAdminService.class);
        AccessTokenResponse response = authzClient.obtainAccessToken(getAdminUsername(), getAdminPassword());
        List<GroupRepresentation> groups = proxy.getUserGroups(getRealm(), userId,
                "Bearer " + response.getToken());
        infos.importUserGroups(groups); //DEBUG here we go!
    } catch (WebApplicationException e) {
        log.error("User groups failure on {}: {}", getRealm(), e.getMessage());
    }
}

But when it comes to data exploration, it turns out that no attributes are provided into the GroupRepresentation#getAttributes structure.

I've read that claims can be added to user info requests. Does it work on the admin API? How can I achieve that result with RESTeasy templates? Thx

Thomas Escolan
  • 1,298
  • 1
  • 10
  • 28
  • I also read this into Keycloak's documentation: "Groups manage groups of users. Attributes can be defined for a group. You can map roles to a group as well. **Users that become members of a group inherit the attributes and role mappings** that group defines"; is it to say that I'd rather look for those attributes directly into user informations? But they don't come up either, so my question remains... – Thomas Escolan May 29 '19 at 14:04
  • Sorry, French holidays... I'll give it a try and feed you back ASAP – Thomas Escolan Jun 03 '19 at 10:28

4 Answers4

39

I was able to achieve this by adding groups/roles info in token other claims property:

For this in keycloak config, go to your client -> mappers & add a group/role mapper. E.g.

enter image description here

Now this info will start coming in your access token:

enter image description here

To access these group attribute in Java you can extract it from otherclaims property of accesstoken. E.g.:

KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext)(request.getAttribute(KeycloakSecurityContext.class.getName()));         
AccesToken token = keycloakSecurityContext.getToken();

In below image you can see that otherclaims property of token is filled with groups attribute that we created on keycloak. Note that if we had named "token claim property" as groupXYZ, the otherclaims would be showing: groupsXYZ=[Administrator]

enter image description here

tryingToLearn
  • 10,691
  • 12
  • 80
  • 114
  • 2
    Fine, I indeed could retrieve all groups attributes into "other claims" as yous said (mixed into one single "groups" entry actually, but that doesn't hurt for my use case) when asking admin API user info (that is to say into UserInfo, not into AccessTokenResponse). Thx a lot! – Thomas Escolan Jun 03 '19 at 10:53
  • 3
    My bad @tryingtolearn ... what I retrieved were group NAMES, BUT what I need is my groups' attributes! – Thomas Escolan Jun 03 '19 at 11:50
  • Should you use groups for permissions? Shouldnt we use roles? Iam not sure about the best approach to set permissions. Iam new to keycloak; Hopefully its not a stupid question. – WastedFreeTime Dec 15 '20 at 15:43
  • 1
    Still very helpful almost three years later, thank you! – CodeShane Dec 31 '21 at 09:42
  • In the new Keycloaks, it is here: Clients → select client → "Client scopes" tab → select / add a scope → add a mapper. – Dmitriy Popov Jul 05 '23 at 13:52
15

This is how I could eventually map group attributes (inherited as user attributes, as suspected before) into user informations, into the "other claims" section :

User Attribute Mapper

Thomas Escolan
  • 1,298
  • 1
  • 10
  • 28
  • did it work for you? The group attributes cannot be mapped using mapper AFAIK... – Tomek Cejner Aug 22 '20 at 00:09
  • 3
    Took me a while to understand this answer. You refer to the following: **Users that become members of a group inherit the attributes that group defines**. That means you can actually do a user attribute mapping and expect to fetch attributes from their groups. Careful though as you might face some conflicts between the names of the group an user attributes – Crystark Dec 15 '20 at 16:48
5

It is possible to inherit attributes from the group by switching on Aggregate attribute values option during the creation of a new User Attribute mapper.

Aggregate attribute values option

Anton Menshov
  • 2,266
  • 14
  • 34
  • 55
Neven
  • 51
  • 1
  • 3
2

First of all I think the answers above are correct. I was able to achieve what I wanted to do by following recommendations from them.

But I have also broke by production keycloak integration with auth2-proxy which lead to some outage for internal users :)

So, I took time to investigate a bit and came up with creating new client scope and adding custom client role / realm role / group mappers to it.

It works, and also you don't break your working keycloak integrations with other services ;)

Here is all my terraform code which you can you to reproduce what I did:


variable "realm_name" {
  type        = string
  description = "Name of the realm to create"
  default     = "master"
}

variable "keycloack_user" {
  type        = string
  description = "Keycloak admin user"
  default     = "admin"
}

variable "keycloack_password" {
  type        = string
  description = "Keycloak admin password"
  default     = "admin"
}

variable "keycloak_url" {
  type        = string
  description = "Keycloak url"
  default     = "http://localhost:8090"
}

variable "oauth_fqdn" {
  type        = string
  description = "FQDN of the oauth server used for valid redirects"
  default     = "http://localhost:3000/*"
}

terraform {
  required_version = ">= 1.0.0"

  required_providers {
    keycloak = {
      source  = "mrparkers/keycloak"
      version = ">= 3.7.0"
    }
  }
}



provider "keycloak" {
  client_id = "admin-cli"
  username  = var.keycloack_user
  password  = var.keycloack_password
  url       = var.keycloak_url
  realm     = var.realm_name
  #   base_path     = "/auth"
}

data "keycloak_realm" "realm" {
  realm = var.realm_name
}

resource "keycloak_openid_client" "client" {
  realm_id  = data.keycloak_realm.realm.id
  client_id = "my-client"

  name    = "my-client"
  enabled = true

  access_type = "CONFIDENTIAL"
  valid_redirect_uris = [
    var.oauth_fqdn
  ]

  login_theme           = "keycloak"
  standard_flow_enabled = true
}


output "keycloak_client_id" {
  value = keycloak_openid_client.client.client_id
}

output "keycloak_client_secret" {
  value     = keycloak_openid_client.client.client_secret
  sensitive = true
}

// creating custom scope
resource "keycloak_openid_client_scope" "this" {
  realm_id               = data.keycloak_realm.realm.id
  name                   = "group_and_roles"
  description            = "When requested, this scope will map a user's group memberships and all roles to a claim"
  include_in_token_scope = true
}

// creating custom group mapper
resource "keycloak_generic_protocol_mapper" "groups" {
  realm_id        = data.keycloak_realm.realm.id
  client_scope_id = keycloak_openid_client_scope.this.id
  name            = "groups mapper"
  protocol        = "openid-connect"
  protocol_mapper = "oidc-group-membership-mapper"
  config = {
    "full.path" : "true",
    "id.token.claim" : "true",
    "access.token.claim" : "true",
    "claim.name" : "groups",
    "userinfo.token.claim" : "true"
  }
}

// creating custom role mapper for realm level roles
resource "keycloak_generic_protocol_mapper" "realm_roles" {
  realm_id        = data.keycloak_realm.realm.id
  client_scope_id = keycloak_openid_client_scope.this.id
  name            = "realm roles mapper"
  protocol        = "openid-connect"
  protocol_mapper = "oidc-usermodel-realm-role-mapper"
  config = {
    "multivalued" : "true",
    "userinfo.token.claim" : "true",
    "id.token.claim" : "true",
    "access.token.claim" : "true",
    "claim.name" : "realm_roles",
    "jsonType.label" : "String"
  }
}

// creating custom role mapper for client level roles
resource "keycloak_generic_protocol_mapper" "client_roles" {
  realm_id        = data.keycloak_realm.realm.id
  client_scope_id = keycloak_openid_client_scope.this.id
  name            = "client roles mapper"
  protocol        = "openid-connect"
  protocol_mapper = "oidc-usermodel-client-role-mapper"
  config = {
    "multivalued" : "true",
    "userinfo.token.claim" : "true",
    "id.token.claim" : "true",
    "access.token.claim" : "true",
    "claim.name" : "client_roles",
    "jsonType.label" : "String"
  }
}

// adding custom scope to client as optional
resource "keycloak_openid_client_optional_scopes" "client_optional_scopes" {
  realm_id  = data.keycloak_realm.realm.id
  client_id = keycloak_openid_client.client.id

  optional_scopes = [
    "address",
    "phone",
    "offline_access",
    "microprofile-jwt",
    keycloak_openid_client_scope.this.name
  ]
}

And later I setup my application level auth config as follows:

AUTH_ISSUER_URL=http://localhost:8090/realms/master
AUTH_CLIENT_ID=my-client
AUTH_CLIENT_SECRET=client-secret-get-it-from-output
AUTH_SCOPES="profile,email,openid,offline_access,group_and_roles"
Alik Khilazhev
  • 995
  • 6
  • 18