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
andclient_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
, andpolicy_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:
-
Under Native Consoles > Access Management, go to Realms > Realm Name > Services > OAuth2 Provider and switch to the Client Dynamic Registration tab.
-
To allow open registration without an access token, enable Allow Open Dynamic Client Registration.
-
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.
-
Save your work.
-
To change the scopes a client can register, switch to the Advanced tab and update the Client Registration Scope Allowlist field.
-
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:
-
In the Advanced Identity Cloud admin UI, go to Applications and select + Custom Application.
-
Select the sign-in method as OIDC - OpenId Connect and application type as Service.
-
Provide the client application details; for example:
- Name
-
registration-service
- Owners
-
<application-owner>
- Client ID
-
registration-service
- Client Secret
-
forgerock
-
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.
-
Save your work.
Software publisher profile
To enable dynamic registration with a software statement JWT, register a software publisher:
-
Under Native Consoles > Access Management, go to Realms > Realm Name > Applications > OAuth 2.0 > Software Publisher and click Add Software Publisher Agent.
-
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.
-
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
, orHS512
, the Software publisher secret must match thek
value in the JWK.
-
-
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
andjwe
.
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.
-
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 }
-
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:
-
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.
-
-
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.
-
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) andexp
(expiration time) claims.The JWT header is
{"alg":"HS256","typ":"JWT"}
, and the secret issecret
.The resulting encrypted JWT is as follows with lines folded for readability:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 .eyJzdWIiOiJyZWdpc3RyYXJAZXhhbXBsZS5jb20iLCJuYW1lIjoiSm9obiBE b2UiLCJpYXQiOjE2NzUyNDYxOTQsImV4cCI6MTY3NTI0OTc5NCwiaXNzIjoia HR0cHM6Ly9wdWJsaXNoZXIuZXhhbXBsZS5jb20iLCJyZWRpcmVjdF91cmlzIj pbImh0dHBzOi8vY2xpZW50LmV4YW1wbGUuY29tL2NhbGxiYWNrIl19 .7_3nu39GtTTz_RPKZMjj1JuwWWTgeE4Iqx7p3-cfiPg
-
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.