Add user attributes to tokens on Keycloak
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:
{
// ...
"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:
{
// ...
"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
<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.
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.