Add user attributes to tokens on Keycloak

  • Home
  • -
  • Blog
  • -
  • Post
  • -
  • KeycloakUserAttributesTokens
2022 . Jan . 30 / Programming

Based on a true story

This post is based on this question about user attributes and how to add them to tokens on Keycloak.

Using the REST API

Keycloak has its own REST API to handle bascially all the info of our realms, but this time we are focusing only on the user attributes.

We are using cURL on this examples, but the premise is the same: make calls to a REST API. If we want to use Postman, we can import the cURL command by clicking Import > Raw Text.

Admin access token

Before creating users and attributes we have to get the admin access token using:

curl --location --request POST 'http://localhost:8180/auth/realms/master/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=<admin_user>' \
--data-urlencode 'password=<admin_password>' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=admin-cli'

Now we put this token on the Authorization header of each subsequent request as Authorization: Bearer <admin_token>.

Notice the <variables> that have to be replaced with the values we need in all the requests.

Add user with attributes

curl --location --request POST 'http://localhost:8180/auth/admin/realms/<some_realm>/users' \
--header 'Authorization: Bearer <admin_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "some_user",
    "attributes": {
        "phoneNumber": "1234567890"
    },
    "enabled": true,
    "credentials": [
        {
            "temporary": false,
            "value": "raw_password"
        }
    ]
}'

Update user attributes

curl --location --request PUT 'http://localhost:8180/auth/admin/realms/<some_realm>/users/<some_user_id>' \
--header 'Authorization: Bearer <admin_token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "attributes": {
        "phoneNumber": "1234567890"
    }
}'

Phone attribute in token

We can use the phone built-in Client Scope to add the phoneNumber attribute to the access token:

curl --location --request POST 'http://localhost:8180/auth/realms/<some_realm>/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=some_user' \
--data-urlencode 'password=raw_password' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=some_client' \
--data-urlencode 'client_secret=<some_client_secret>' \
--data-urlencode 'scope=phone'

The access token will look like this:

access token
{
    // ...
    "scope": "profile phone email",
    "phone_number": "1234567890",
    "preferred_username": "some_user"
}

If we want to use another name for the phone number attribute, e.g. phone_attr, using the Keycloak Admin Console we'll have to create a new Client Scope that includes a new mapper like this:

Name: some_mapping
Mapper Type: User Attribute
User Attribute: phone_attr
Token Claim Name: phone_attr_in_token
Claim JSON Type: String
Add to access token: ON

And add the new scope to a Client. Edit the Client, go to the Client Scopes tab and assign the new scope either to the Default or the Optional Client Scopes. The default will add the scope to the token even if you don't specify it when asking for a token. The optional needs to be specified.

Now, when asking for a token, the new scope can be used as before. Instead of scope=phone, we use scope=some_scope. Then, the token will look like:

access token
{
    // ...
    "scope": "profile email some_scope",
    "phone_attr_in_token": "1234567890",
    "preferred_username": "some_user"
}

Using the Java API

The Keycloak Java API is a client implementation of the REST API, but instead of directly use the urls and send the data as JSON, we use objects only.

Maven dependencies

pom.xml
<dependencies>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-admin-client</artifactId>
        <version>16.1.0</version>
    </dependency>
</dependencies>

Java Code

On this example we'll only cover the user attributes. It gets the same result as the previous cURL comands.

KeycloakUserAttributes.java
package dev.ralphdeving;

import java.util.Arrays;

import javax.ws.rs.core.Response;

import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;

public class KeycloakUserAttributes {

    public static void main (String[] args) {
        // The admin access
        try (Keycloak keycloakInstance = Keycloak.getInstance(
                "http://localhost:8180/auth",
                "master",
                "<admin_user>",
                "<admin_password>",
                "admin-cli");) {

            String userId = createUser(keycloakInstance);
            System.out.println("<some_user> id: " + userId);

            UserRepresentation userRepresentation = addAttribute(keycloakInstance, userId);

            System.out.println("<some_user> phoneNumber: " + userRepresentation.firstAttribute("phoneNumber"));

            System.out.println("<some_user> phone_attr: " + userRepresentation.firstAttribute("phone_attr"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 
     */
    public static final String REALM = "<some_realm>";

    /**
     * 
     */
    public static String createUser (Keycloak keycloakInstance) {
        // New user with attributes
        UserRepresentation userRepresentation = new UserRepresentation();
        userRepresentation.setUsername("<some_user>");
        userRepresentation.singleAttribute("phoneNumber", "1234567890");
        userRepresentation.setEnabled(true);

        // Password for the new user
        CredentialRepresentation credentialRepresentation = new CredentialRepresentation();
        credentialRepresentation.setType(CredentialRepresentation.PASSWORD);
        credentialRepresentation.setValue("<raw_password>");
        credentialRepresentation.setTemporary(false);

        userRepresentation.setCredentials(Arrays.asList(credentialRepresentation));

        // Realm where the user is going to be registered
        RealmResource realmResource = keycloakInstance.realm(REALM);
        UsersResource usersResource = realmResource.users();

        // Now we get the created user ID like:
        // https://github.com/keycloak/keycloak/blob/2.5.0.Final/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java#L52-L64
        Response response = usersResource.create(userRepresentation);
        String path = response.getLocation().getPath();
        String userId = path.substring(path.lastIndexOf('/') + 1);
        try {
            response.close();
        } catch (Exception e) {}

        return userId;
    }

    /**
     * 
     */
    public static UserRepresentation addAttribute (Keycloak keycloakInstance, String userId) {
        RealmResource realmResource = keycloakInstance.realm(REALM);
        UsersResource usersResource = realmResource.users();

        // Notice the object type User()Resource vs User(s)Resource
        UserResource userResource = usersResource.get(userId);

        UserRepresentation userRepresentation = userResource.toRepresentation();

        // Add new attribute
        userRepresentation.singleAttribute("phone_attr", "9876543210");

        userResource.update(userRepresentation);

        return userRepresentation;
    }
}

Conclusion

Now we have some basics of how to add attributes to a user on Keycloak and add them to their tokens.

Comments
If you have any doubt or question, or know how to improve this post, please feel free to write a comment.