Adding Custom Claims to Userinfo API Responses

Adding Custom Claims to Userinfo API Responses

Introduction

The claims returned by the Userinfo API can be customized with the claims parameter just like those in How to Add Custom Claims to ID Tokens.

This article explains how to add arbitrary claims to the Userinfo endpoint response and which Authlete features help determine the right claims.

Basic Implementation of the Userinfo Endpoint

When an OpenID Provider builds a Userinfo API endpoint with Authlete, it needs to call the following two APIs:

Authlete’s Userinfo Request endpoint validates the presented access token and returns the user information tied to that token. The OpenID Provider inspects the Userinfo Request response to confirm the token validity and the associated subject, retrieves the subject’s attributes from its data store, and submits them to the Issue Userinfo Response API.

Calling the Userinfo Request API

In practice, the OpenID Provider forwards the user’s access token to Authlete’s Userinfo Request API:

curl --request POST \
  --url https://jp.authlete.com/api/{{serviceId}}/auth/userinfo \
  --header 'authorization: Bearer {{serviceAccessToken}}' \
  --header 'content-type: application/json' \
  --data '{"token": "{{accessToken}}"}'

If the access token is valid, the response looks like the following:

{
  "action": "OK",
  "clientId": 2064424232,
  "subject": "john",
  "scopes": [
    "openid",
    "email"
  ],
  "claims": [
    "email",
    "email_verified"
  ],
  "token": "{{accessToken}}",
  "clientIdAlias": "2064424232",
  "clientIdAliasUsed": true,
  "consentedClaims": [
    "email",
    "email_verified"
  ],
  "clientEntityIdUsed": false,
  "resultCode": "A091001",
  "resultMessage": "[A091001] The access token presented at the userinfo endpoint is valid."
}

Here action is OK, which proves the token is valid. Because the subject is john, the OpenID Provider can collect the attributes linked to john and provide them to the Issue Userinfo Response API.

Calling the Issue Userinfo Response API

Since Authlete does not store attribute values linked to users, the OpenID Provider must prepare the attribute values to be included in the response from the UserInfo endpoint. On the other hand, the OpenID Provider can use the claims parameter returned by Authlete to determine which attributes to include in the UserInfo response.

The claims parameter contained in the Userinfo Request API response is a merged list of claims derived from the scopes specified at the /auth/authorization endpoint and claims explicitly requested via the claims parameter, just like the logic described in Determining Claims to Add to an ID Token.

In the previous example, the requested scope included openid and email, so the email scope expanded to email and email_verified, and both claims appeared in the claims array. For the list of scopes and their corresponding claims, refer to the Authlete Java Common Javadoc ClaimsScope.

Below is an example where the OpenID Provider injects the following claims from Authlete’s claims response into the Userinfo payload:

Claim Value
“email” john@example.com
“email_verified” true

The request that adds these claims (line breaks inserted for readability) looks like this:

curl --request POST \
  --url https://jp.authlete.com/api/{{serviceId}}/auth/userinfo/issue \
  --header 'authorization: Bearer {{serviceAccessToken}}' \
  --header 'content-type: application/json' \
  --data '{"token": "{{accessToken}}",
    "claims": "{
      \"email\": \"john@example.com\",
      \"email_verified\": true}"
 }'

A successful response shows that the specified claims were embedded in responseContent:

{
  "action": "JSON",
  "responseContent": "{\"sub\":\"john\",\"email\":\"john@example.com\",\"email_verified\":true}",
  "resultCode": "A096001",
  "resultMessage": "[A096001] User information was obtained successfully."
}

Customizing the List of Claims to Include

The claims array calculated by Authlete follows OpenID Connect Core, but it may not match real-world requirements. For example, you might need to return a different set of claims from the Userinfo endpoint than from the ID token, or you may accept only the openid and email scopes while still needing to send additional user-consented claims in the Userinfo response.

In that case, the OpenID Provider can leverage consentedClaims to propagate the claims that the end-user allowed the RP to access at the authorization endpoint to both the Introspection and Userinfo endpoints.

consentedClaims is available in Authlete 2.3 and later. For details on consentedClaims, please refer to the Authlete JavaDoc: AuthorizationIssueRequest.html#setConsentedClaims().

Telling Authlete Which Claims the User Consented To

Assume that during authorization the user agreed to share their full name, username, and email address with the RP, and the OpenID Provider wants to return those attributes from the Userinfo endpoint.

The authorization endpoint first sends the following request to Authlete and receives a ticket:

curl --request POST \
  --url https://jp.authlete.com/api/{{serviceId}}/auth/authorization \
  --header 'authorization: Bearer {{serviceAccessToken}}' \
  --header 'content-type: application/json' \
  --data '{"parameters": "client_id=rest-client-client&scope=openid%20email&response_type=code&redirect_uri=https://example.com&state=xyz&nonce=12345"}'

Partial response:

{
  "action": "INTERACTION",
  "service": {
  },
  "client": {
  },
  "display": "PAGE",
  "maxAge": 0,
  "scopes": [
    {
      "name": "email",
      "defaultEntry": false,
      "description": "A permission to request an OpenID Provider to include the email claim and the email_verified claim in an ID Token. See OpenID Connect Core 1.0, 5.4. for details."
    },
    {
      "name": "openid",
      "defaultEntry": false,
      "description": "A permission to request an OpenID Provider to issue an ID Token. See OpenID Connect Core 1.0, 3.1.2.1. for details."
    }
  ],
  "ticket": "IWjy7qiCFJsHYHBKAfwJNLHfoV86lL7KYC0ojmi-Qrk",
  "resultCode": "A004001",
  "resultMessage": "[A004001] Authlete has successfully issued a ticket to the service (API Key = 666990377) for the authorization request from the client (ID = 1110197986). [response_type=code, openid=true]"
}

After obtaining the user’s consent to share their full name, username, and email address, the OpenID Provider calls the auth/authorization/issue endpoint. It passes consentedClaims with name, username, email, and email_verified.

curl --request POST \
  --url https://jp.authlete.com/api/{{serviceId}}/auth/authorization \
  --header 'authorization: Bearer {{serviceAccessToken}}' \
  --header 'content-type: application/json' \
  --data '{
    "ticket": "{{ticket}}",
    "subject": "john",
    "claims": "{\"claim_for_id_token\":\"value_for_id_token\"}",
    "consentedClaims": ["name", "username", "email", "email_verified"]
}'

Authlete stores the consentedClaims specified at authorization time and later includes them in the responses from /auth/userinfo and /auth/introspection.

Ensure that any claim you plan to list in consentedClaims has already been registered under the target service’s Tokens & Claims > Claims > Supported Claims settings.

supported claims
>

Using Consented Claims in the Userinfo API Response

When the access token tied to the above authorization flow is sent to the Userinfo Request API, the response is as follows:

curl --request POST \
  --url https://jp.authlete.com/api/{{serviceId}}/auth/userinfo \
  --header 'authorization: Bearer {{serviceAccessToken}}' \
  --header 'content-type: application/json' \
  --data '{"token": "{{accessToken}}"}'
{
  "action": "OK",
  "clientId": 1110197986,
  "subject": "john",
  "scopes": [
    "email",
    "openid"
  ],
  "claims": [
    "email",
    "email_verified"
  ],
  "token": "ytaniPwocUVRSDuCBn6EEIR7SYYRxIHkmvv2ht7oPLU",
  "clientIdAlias": "rest-client-client",
  "clientIdAliasUsed": true,
  "clientEntityIdUsed": false,
  "consentedClaims": [
    "name",
    "username",
    "email",
    "email_verified"
  ],
  "resultCode": "A091001",
  "resultMessage": "[A091001] The access token presented at the userinfo endpoint is valid."
}

Instead of the claims array, the OpenID Provider can use the consentedClaims array and return the claims the user agreed to share as the Userinfo endpoint payload.

curl --request POST \
  --url https://jp.authlete.com/api/{{serviceId}}/auth/userinfo/issue \
  --header 'authorization: Bearer {{serviceAccessToken}}' \
  --header 'content-type: application/json' \
  --data '{"token": "{{accessToken}}",
    "claims": "{
      \"name\": \"John Doe\",
      \"username\": \"jdoe\",
      \"email\": \"john@example.com\",
      \"email_verified\": true}"
 }'

As a result of the above request, the Userinfo endpoint returns a response containing the user’s name, username, and email address, which the user has consented to provide, as shown below.

{
  "action": "JSON",
  "responseContent": "{\"sub\":\"john\",\"name\":\"John Doe\",\"email\":\"john@example.com\",\"email_verified\":true,\"username\":\"jdoe\"}",
  "resultCode": "A096001",
  "resultMessage": "[A096001] User information was obtained successfully."
}