PingOne Advanced Identity Cloud

Dynamic client registration

PingOne Advanced Identity Cloud supports dynamic registration. RFC 7591 OAuth 2.0 Dynamic Client Registration Protocol and OpenID Connect Dynamic Client Registration 1.0 describe the dynamic registration options for OAuth 2.0 and OpenID Connect client applications.

PingOne Advanced Identity Cloud returns an error when a dynamic client registration request payload includes incorrect information or specifies unsupported signing and encryption algorithms. For example, if a public client requests symmetric signing or encryption, the request results in an error because public clients cannot have a client secret to use for symmetric encryption.

Dynamic registration options

Open registration

The application registers its profile without an access token.

PingOne Advanced Identity Cloud generates client_id and client_secret values. PingOne Advanced Identity Cloud ignores any values provided in the profile for these properties.

You can use this method to develop and test client registration. This method does not limit the number of client registrations. If you use it in production, also require a software statement.

Registration with an access token

The application registers its profile with an access token for authorization.

The specification does not describe how the client obtains the access token. You register an initial OAuth 2.0 client application manually, and use this application to obtain the access token on behalf of the client requesting registration.

To register the logo_uri, client_uri, and policy_uri the access token must include a special scope; default: dynamic_client_registration.

Registration with a software statement

The application registers its profile with a software statement.

A software statement is a JSON Web Token (JWT) that holds registration claims about the client, such as its issuer and redirection URIs.

A software statement is issued by a software publisher. The software publisher encrypts and signs the claims in the software statement.

You store software publisher details in a software publisher profile. The software publisher profile identifies the issuer included in software statements. It provides access to the secret or the keys to decrypt software statement JWTs and to verify their signatures. When the client registers dynamically with a software statement, PingOne Advanced Identity Cloud uses the software publisher profile to determine whether it can trust the software statement.

The protocol specification does not describe how the client obtains the software statement JWT. PingOne Advanced Identity Cloud expects the software publisher to construct the JWT according to the settings in its profile.

Enable dynamic client registration

Option Create or update...

Open registration

Registration with an access token

Registration requires a software statement JWT

OAuth 2.0 provider settings

To enable open registration and registration with a software statement, update the OAuth 2.0 provider configuration for the realm:

  1. Under Native Consoles > Access Management, go to Realms > Realm Name > Services > OAuth2 Provider and switch to the Client Dynamic Registration tab.

  2. To allow open registration without an access token, enable Allow Open Dynamic Client Registration.

  3. To require a software statement to register, enable Require Software Statement for Dynamic Client Registration, and edit the Required Software Statement Attested Attributes list to include all the required claims.

  4. Save your work.

  5. To change the scopes a client can register, switch to the Advanced tab and update the Client Registration Scope Allowlist field.

  6. Save your work.

For additional details, refer to the Client dynamic registration reference.

Client profile for access tokens

To enable dynamic registration with an access token, manually register a service application to provide the access tokens:

  1. In the Advanced Identity Cloud admin UI, go to Applications and select + Custom Application.

  2. Select the sign-in method as OIDC - OpenId Connect and application type as Service.

  3. Provide the client application details; for example:

    Name

    registration-service

    Owners

    <application-owner>

    Client ID

    registration-service

    Client Secret

    forgerock

  4. Under Sign On > General Settings, add the special scope:

    Scopes

    dynamic_client_registration

    If the string for the special scope is not the default, use the scope specified in the OAuth 2.0 provider configuration Client Dynamic Registration > Scope to give access to dynamic client registration field.

  5. Save your work.

Software publisher profile

To enable dynamic registration with a software statement JWT, register a software publisher:

  1. Under Native Consoles > Access Management, go to Realms > Realm Name > Applications > OAuth 2.0 > Software Publisher and click Add Software Publisher Agent.

  2. Add the basic settings as necessary before you click Create:

    Agent ID

    Required identifier for the profile.

    Software publisher secret

    Secret required when the publisher uses HMAC symmetric encryption for the JWTs.

    Software publisher issuer

    Required issuer identifier to match the iss claim in JWTs.

  3. Configure the appropriate security settings:

    • If you provide the JSON Web Key (JWK) by URI rather than by value, where the Public key selector is JWKs_URI, PingOne Advanced Identity Cloud must access the JWKs when processing registration requests.

    • If the publisher uses symmetric encryption, where the Software statement signing Algorithm is HS256, HS384, or HS512, the Software publisher secret must match the k value in the JWK.

  4. Save your work.

The software publisher provides client applications using dynamic registration with a valid software statement JWT. Valid software statement JWTs must have:

  • All the required claims listed in the OAuth 2.0 provider’s Required Software Statement Attested Attributes.

  • An issuer (iss) claim matching a publisher profile’s Software publisher issuer.

These constraints apply to software statement JWTs:

  • Compressed JWTs must not be larger than 32 KiB (32768 bytes) when uncompressed.

  • Advanced Identity Cloud ignores keys specified in JWT headers, such as jku and jwe.

Registration examples

Review the following dynamic client registration examples.

The client must read and store the dynamic registration response. The response includes important information about the client, such as:

  • The generated client ID and the generated client secret for confidential clients.

    You cannot choose the client ID or client secret when registering an application dynamically.

  • The URL and access token required to update the client profile.

Open registration

The following example depends on an update to OAuth 2.0 provider settings. Once you have enabled Allow Open Dynamic Client Registration, register a client dynamically.

Include a client_name in the payload as the human-readable name to display to resource owners:

$ curl \
--request POST \
--header 'Content-Type: application/json' \
--data '{
  "redirect_uris": ["https://client.example.com/callback"],
  "client_name#en": "My Client",
  "client_name#ja-Jpan-JP": "\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u540D",
  "client_uri": "https://client.example.com/"
}' \
'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/register'
Show the response
{
  "authorization_signed_response_alg": "RS256",
  "request_object_encryption_alg": "",
  "introspection_encrypted_response_alg": "RSA-OAEP-256",
  "client_uri": "https://client.example.com/",
  "default_max_age": 1,
  "application_type": "web",
  "introspection_encrypted_response_enc": "A128CBC-HS256",
  "introspection_signed_response_alg": "RS256",
  "client_name#en": "My Client",
  "userinfo_encrypted_response_enc": "",
  "registration_client_uri": "https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/register?client_id=<generated-client-id>",
  "client_type": "Confidential",
  "userinfo_encrypted_response_alg": "",
  "registration_access_token": "<generated-registration-access-token>",
  "client_id": "<generated-client-id>",
  "token_endpoint_auth_method": "client_secret_basic",
  "userinfo_signed_response_alg": "",
  "public_key_selector": "x509",
  "scope": "address phone openid profile email",
  "require_pushed_authorization_requests": false,
  "authorization_code_lifetime": 0,
  "client_secret": "<generated-client-secret>",
  "user_info_response_format_selector": "JSON",
  "tls_client_certificate_bound_access_tokens": false,
  "backchannel_logout_session_required": false,
  "id_token_signed_response_alg": "RS256",
  "default_max_age_enabled": false,
  "token_intro_response_format_selector": "JSON",
  "subject_type": "public",
  "grant_types": ["authorization_code"],
  "jwt_token_lifetime": 0,
  "id_token_encryption_enabled": false,
  "redirect_uris": ["https://client.example.com/callback"],
  "jwks_cache_miss_cache_time": 60000,
  "jwks_cache_timeout": 3600000,
  "client_name#ja-jpan-jp": "クライアント名",
  "id_token_encrypted_response_alg": "RSA-OAEP-256",
  "id_token_encrypted_response_enc": "A128CBC-HS256",
  "client_secret_expires_at": 0,
  "access_token_lifetime": 0,
  "refresh_token_lifetime": 0,
  "scopes": ["address", "phone", "openid", "profile", "email"],
  "request_object_signing_alg": "",
  "response_types": ["code"]
}

OpenID Connect clients must include these claims in the JSON registration data:

  • The openid scope; for example, "scopes": ["profile", "openid"].

  • The id_token response type; for example, "response_types": ["code", "id_token code"].

Registration with an access token

The following example depends on a Client profile for access tokens.

  1. Use the registration service client to get an access token:

    $ curl \
    --request POST \
    --user 'registration-service:forgerock' \
    --data 'grant_type=client_credentials' \
    --data 'scope=dynamic_client_registration' \
    'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/access_token'
    {
      "access_token": "<access-token>",
      "scope": "dynamic_client_registration",
      "token_type": "Bearer",
      "expires_in": 3596
    }
  2. Register a client dynamically with the access token as authorization.

    Include a client_name in the payload as the human-readable name to display to resource owners.

    $ curl \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <access-token>' \
    --data '{
      "redirect_uris": ["https://client.example.com/callback"],
      "client_name#en": "My Client",
      "client_name#ja-Jpan-JP": "\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u540D",
      "client_uri": "https://client.example.com/"
    }' \
    'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/register'
    Show the response
    {
         "request_object_encryption_alg": "",
         "default_max_age": 1,
         "application_type": "web",
         "client_name#en": "My Client",
         "registration_client_uri": "https://<tenant-env-fqdn>/am/oauth2/register?client_id=<generated-client-id>",
         "client_type": "Confidential",
         "userinfo_encrypted_response_alg": "",
         "registration_access_token": "<generated-registration-access-token>",
         "client_id": "<generated-client-id>",
         "token_endpoint_auth_method": "client_secret_basic",
         "userinfo_signed_response_alg": "",
         "public_key_selector": "x509",
         "authorization_code_lifetime": 0,
         "client_secret": "<generated-client-secret>",
         "user_info_response_format_selector": "JSON",
         "id_token_signed_response_alg": "HS256",
         "default_max_age_enabled": false,
         "subject_type": "public",
         "jwt_token_lifetime": 0,
         "id_token_encryption_enabled": false,
         "redirect_uris": ["https://client.example.com/callback"],
         "client_name#ja-jpan-jp": "クライアント名",
         "id_token_encrypted_response_alg": "RSA1_5",
         "id_token_encrypted_response_enc": "A128CBC_HS256",
         "client_secret_expires_at": 0,
         "access_token_lifetime": 0,
         "refresh_token_lifetime": 0,
         "request_object_signing_alg": "",
         "response_types": ["code"]
     }

    OpenID Connect clients must include these claims in the JSON registration data:

    • The openid scope; for example, "scopes": ["profile", "openid"].

    • The id_token response type; for example, "response_types": ["code", "id_token code"].

Registration with a software statement

The following example depends on an update to OAuth 2.0 provider settings, a Software publisher profile, and an encrypted software statement JWT:

  1. Configure the OAuth 2.0 provider:

    This example uses open registration with a software statement. The OAuth 2.0 provider has these settings enabled:

    • Allow Open Dynamic Client Registration

    • Require Software Statement for Dynamic Client Registration

    If you leave Allow Open Dynamic Client Registration disabled, use an access token as authorization for the registration request, as demonstrated in Registration with an access token.

  2. Configure the software publisher account.

    The software publisher for this example has the following profile settings:

    Agent ID

    My Software Publisher

    Software publisher secret

    secret

    Software publisher issuer

    https://publisher.example.com

    Software statement signing Algorithm

    HS256

    Public key selector

    JWKs

    Json Web Key

    {"keys":[{"kty":"oct","k":"secret","alg":"HS256"}]}

    Notice that the value is a key set rather than a single key.

  3. Prepare the software statement.

    The plaintext payload of the software statement JWT in this example is the following:

    {
      "sub": "registrar@example.com",
      "name": "My Client",
      "iat": 1675246194,
      "exp": 1675249794,
      "iss": "https://publisher.example.com",
      "redirect_uris": ["https://client.example.com/callback"]
    }

    When you try the example, use current values for the iat (issued at) and exp (expiration time) claims.

    The JWT header is {"alg":"HS256","typ":"JWT"}, and the secret is secret.

    The resulting encrypted JWT is as follows with lines folded for readability:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    .eyJzdWIiOiJyZWdpc3RyYXJAZXhhbXBsZS5jb20iLCJuYW1lIjoiSm9obiBE
    b2UiLCJpYXQiOjE2NzUyNDYxOTQsImV4cCI6MTY3NTI0OTc5NCwiaXNzIjoia
    HR0cHM6Ly9wdWJsaXNoZXIuZXhhbXBsZS5jb20iLCJyZWRpcmVjdF91cmlzIj
    pbImh0dHBzOi8vY2xpZW50LmV4YW1wbGUuY29tL2NhbGxiYWNrIl19
    .7_3nu39GtTTz_RPKZMjj1JuwWWTgeE4Iqx7p3-cfiPg
  4. Register a client dynamically with the software statement JWT:

    $ curl \
    --request POST \
    --header 'Content-Type: application/json' \
    --data '{
      "redirect_uris": ["https://client.example.com/callback"],
      "client_name#en": "My Client",
      "client_name#ja-Jpan-JP": "\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u540D",
      "client_uri": "https://client.example.com/",
      "software_statement": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWdpc3RyYXJAZXhhbXBsZS5jb20iLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE2NzUyNDYxOTQsImV4cCI6MTY3NTI0OTc5NCwiaXNzIjoiaHR0cHM6Ly9wdWJsaXNoZXIuZXhhbXBsZS5jb20iLCJyZWRpcmVjdF91cmlzIjpbImh0dHBzOi8vY2xpZW50LmV4YW1wbGUuY29tL2NhbGxiYWNrIl19.7_3nu39GtTTz_RPKZMjj1JuwWWTgeE4Iqx7p3-cfiPg"
    }' \
    'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/register'
    Show the response
    {
      "authorization_signed_response_alg": "RS256",
      "request_object_encryption_alg": "",
      "introspection_encrypted_response_alg": "RSA-OAEP-256",
      "client_uri": "https://client.example.com/",
      "default_max_age": 1,
      "application_type": "web",
      "introspection_encrypted_response_enc": "A128CBC-HS256",
      "introspection_signed_response_alg": "RS256",
      "client_name#en": "My Client",
      "userinfo_encrypted_response_enc": "",
      "registration_client_uri": "https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/register?client_id=<generated-client-id>",
      "client_type": "Confidential",
      "userinfo_encrypted_response_alg": "",
      "registration_access_token": "<generated-registration-access-token>",
      "client_id": "<generated-client-id>",
      "token_endpoint_auth_method": "client_secret_basic",
      "userinfo_signed_response_alg": "",
      "software_statement": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWdpc3RyYXJAZXhhbXBsZS5jb20iLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE2NzUyNDYxOTQsImV4cCI6MTY3NTI0OTc5NCwiaXNzIjoiaHR0cHM6Ly9wdWJsaXNoZXIuZXhhbXBsZS5jb20iLCJyZWRpcmVjdF91cmlzIjpbImh0dHBzOi8vY2xpZW50LmV4YW1wbGUuY29tL2NhbGxiYWNrIl19.7_3nu39GtTTz_RPKZMjj1JuwWWTgeE4Iqx7p3-cfiPg",
      "public_key_selector": "x509",
      "scope": "address phone openid profile email",
      "require_pushed_authorization_requests": false,
      "authorization_code_lifetime": 0,
      "client_secret": "<generated-client-secret>",
      "user_info_response_format_selector": "JSON",
      "tls_client_certificate_bound_access_tokens": false,
      "backchannel_logout_session_required": false,
      "id_token_signed_response_alg": "RS256",
      "default_max_age_enabled": false,
      "token_intro_response_format_selector": "JSON",
      "subject_type": "public",
      "grant_types": ["authorization_code"],
      "jwt_token_lifetime": 0,
      "id_token_encryption_enabled": false,
      "redirect_uris": ["https://client.example.com/callback"],
      "jwks_cache_miss_cache_time": 60000,
      "jwks_cache_timeout": 3600000,
      "client_name#ja-jpan-jp": "クライアント名",
      "id_token_encrypted_response_alg": "RSA-OAEP-256",
      "id_token_encrypted_response_enc": "A128CBC-HS256",
      "client_secret_expires_at": 0,
      "access_token_lifetime": 0,
      "refresh_token_lifetime": 0,
      "scopes": ["address", "phone", "openid", "profile", "email"],
      "request_object_signing_alg": "",
      "response_types": ["code"]
    }

    OpenID Connect clients must include these claims in the JSON registration data:

    • The openid scope; for example, "scopes": ["profile", "openid"].

    • The id_token response type; for example, "response_types": ["code", "id_token code"].

Manage client profiles

The JSON response to a successful dynamic registration request contains the following fields:

registration_client_uri

The endpoint for reading and updating the client profile, including the generated client ID as a query parameter.

registration_access_token

The generated access token to authorize reading and updating the client profile.

Make sure your client application stores the dynamic registration response, including these values. Your application needs them to read and update its client profile.

Read a client profile

To read a client profile, send an HTTP GET request to the registration_client_uri with the registration_access_token for authorization:

$ curl \
--request GET \
--header 'Authorization: Bearer <generated-registration-access-token>' \
"https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/register?client_id=<generated-client-id>"
Show the response
{
  "authorization_signed_response_alg": "RS256",
  "request_object_encryption_alg": "",
  "introspection_encrypted_response_alg": "RSA-OAEP-256",
  "client_uri": "https://client.example.com/",
  "default_max_age": 1,
  "application_type": "web",
  "introspection_encrypted_response_enc": "A128CBC-HS256",
  "introspection_signed_response_alg": "RS256",
  "client_name#en": "My Client",
  "userinfo_encrypted_response_enc": "",
  "client_type": "Confidential",
  "userinfo_encrypted_response_alg": "",
  "token_endpoint_auth_method": "client_secret_basic",
  "userinfo_signed_response_alg": "",
  "client_id": "<generated-client-id>",
  "public_key_selector": "x509",
  "scope": "openid address phone email profile",
  "require_pushed_authorization_requests": false,
  "authorization_code_lifetime": 0,
  "client_secret": "<generated-client-secret>",
  "user_info_response_format_selector": "JSON",
  "tls_client_certificate_bound_access_tokens": false,
  "backchannel_logout_session_required": false,
  "id_token_signed_response_alg": "RS256",
  "default_max_age_enabled": false,
  "token_intro_response_format_selector": "JSON",
  "subject_type": "public",
  "grant_types": ["authorization_code"],
  "jwt_token_lifetime": 0,
  "id_token_encryption_enabled": false,
  "redirect_uris": ["https://client.example.com/callback"],
  "jwks_cache_miss_cache_time": 60000,
  "jwks_cache_timeout": 3600000,
  "client_name#ja-jpan-jp": "クライアント名",
  "id_token_encrypted_response_alg": "RSA-OAEP-256",
  "id_token_encrypted_response_enc": "A128CBC-HS256",
  "client_secret_expires_at": 0,
  "access_token_lifetime": 0,
  "refresh_token_lifetime": 0,
  "scopes": ["openid", "address", "phone", "email", "profile"],
  "request_object_signing_alg": "",
  "response_types": ["code"]
}

The response does not contain the registration_client_uri or the registration_access_token.

Update a client profile

When an application updates its client profile rather than registering again dynamically, it retains the current client ID and client secret.

The update request body replaces the current client profile settings subject to the these conditions:

  • Updates cannot change any of the following settings:

    • client_id_issued_at

    • client_secret

    • client_secret_expires_at

    • registration_access_token

    • registration_client_uri

  • Missing settings are set to their default values.

  • Settings with unrecognized names are silently ignored.

  • If the client profile includes a software statement JWT, it must be valid and current.

  • A successful update returns a new registration access token to use going forward.

To update a client profile, send an HTTP PUT request to the registration_client_uri with the registration_access_token for authorization and the request body specifying the new settings.

The following example updates the scope and grant_types settings:

$ curl \
--request PUT \
--header 'Authorization: Bearer <generated-registration-access-token>' \
--data '{
  "client_name#en": "My Client",
  "client_name#ja-jpan-jp": "クライアント名",
  "client_id": "<generated-client-id>",
  "client_secret": "<generated-client-secret>",
  "client_uri": "https://client.example.com/",
  "scope": "openid profile",
  "grant_types": ["authorization_code", "implicit"],
  "redirect_uris": ["https://client.example.com/callback"]
}' \
'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/register?client_id=<generated-client-id>'
Show the response
{
  "authorization_signed_response_alg": "RS256",
  "request_object_encryption_alg": "",
  "introspection_encrypted_response_alg": "RSA-OAEP-256",
  "client_uri": "https://client.example.com/",
  "default_max_age": 1,
  "application_type": "web",
  "introspection_encrypted_response_enc": "A128CBC-HS256",
  "introspection_signed_response_alg": "RS256",
  "client_name#en": "My Client",
  "userinfo_encrypted_response_enc": "",
  "client_type": "Confidential",
  "userinfo_encrypted_response_alg": "",
  "registration_access_token": "<generated-registration-access-token>",
  "client_id": "<generated-client-id>",
  "token_endpoint_auth_method": "client_secret_basic",
  "userinfo_signed_response_alg": "",
  "public_key_selector": "x509",
  "scope": "openid profile",
  "require_pushed_authorization_requests": false,
  "authorization_code_lifetime": 0,
  "client_secret": "<generated-client-secret>",
  "user_info_response_format_selector": "JSON",
  "tls_client_certificate_bound_access_tokens": false,
  "backchannel_logout_session_required": false,
  "id_token_signed_response_alg": "RS256",
  "default_max_age_enabled": false,
  "token_intro_response_format_selector": "JSON",
  "subject_type": "public",
  "grant_types": ["authorization_code", "implicit"],
  "jwt_token_lifetime": 0,
  "id_token_encryption_enabled": false,
  "redirect_uris": ["https://client.example.com/callback"],
  "jwks_cache_miss_cache_time": 60000,
  "jwks_cache_timeout": 3600000,
  "client_name#ja-jpan-jp": "クライアント名",
  "id_token_encrypted_response_alg": "RSA-OAEP-256",
  "id_token_encrypted_response_enc": "A128CBC-HS256",
  "access_token_lifetime": 0,
  "refresh_token_lifetime": 0,
  "scopes": ["openid", "profile"],
  "request_object_signing_alg": "",
  "response_types": ["code"]
}

The registration_access_token in the response reflects the new value to use going forward.

Delete a client profile

To remove a client profile, send an HTTP DELETE request to the registration_client_uri with the registration_access_token for authorization:

$ curl \
--request DELETE \
--header 'Authorization: Bearer <generated-registration-access-token>' \
'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/register?client_id=<generated-client-id>'

A successful request returns an HTTP 204 No Content response.

Authorization grants and active tokens associated with the client remain valid until they expire.

Copyright © 2010-2024 ForgeRock, all rights reserved.