Skip to content

Dynamic Client Registration

Dick Davis edited this page Jan 25, 2026 · 3 revisions

Dynamic Client Registration

Dynamic Client Registration (RFC 7591) allows OAuth clients to register themselves programmatically without manual intervention. This is useful for automated deployments and third-party integrations.

Process

---
title: Dynamic Client Registration
---
sequenceDiagram
autonumber

actor Client
participant Authorization Service
participant Data Layer

Client-->>Authorization Service: Calls the registration endpoint with client metadata
Note right of Client: POST /oauth/register
Authorization Service-->Authorization Service: Validates initial access token (if required)
Authorization Service-->Authorization Service: Validates client metadata
Authorization Service-->Authorization Service: Validates software statement (if provided)
Authorization Service-->>Data Layer: Creates new Client record
Authorization Service-->>Client: Returns client credentials and metadata
Loading

Client

The Client entity represents a client application that wishes to register with the OAuth provider.

Authorization Service

The Authorization Service entity is responsible for validating the registration request and creating the client record. This is provided by TokenAuthority.

Data Layer

The Data Layer entity is a relational database used for storing client registration data.

Endpoints

POST /oauth/register

The client calls the POST /oauth/register endpoint to register a new OAuth client. The endpoint responds with either an error or a JSON object containing the client credentials and echoed metadata.

HTTP Method: POST

URL: /oauth/register

Content-Type: application/json

Authentication: If configured, an initial access token may be required via the Authorization: Bearer <token> header.

Request Body:

Field Required? Description
redirect_uris yes Array of redirect URIs for the client. Must be valid HTTP/HTTPS URLs.
token_endpoint_auth_method no Authentication method for the token endpoint. Default: client_secret_basic.
grant_types no Array of grant types the client will use. Default: ["authorization_code"].
response_types no Array of response types. Default: ["code"].
client_name no Human-readable name of the client.
client_uri no URL of the client's home page.
logo_uri no URL of the client's logo image.
tos_uri no URL of the client's terms of service.
policy_uri no URL of the client's privacy policy.
contacts no Array of email addresses for responsible parties.
scope no Space-separated list of scopes the client may request.
jwks_uri no URL of the client's JSON Web Key Set (for private_key_jwt auth).
jwks no JSON Web Key Set document (for private_key_jwt auth). Mutually exclusive with jwks_uri.
software_id no Unique identifier for the client software.
software_version no Version of the client software.
software_statement no Signed JWT containing client metadata claims.

Supported Authentication Methods:

Method Client Type Description
none Public No authentication (for public clients).
client_secret_basic Confidential HTTP Basic authentication with client_id and client_secret.
client_secret_post Confidential Client credentials in POST body.
private_key_jwt Confidential JWT assertion signed with client's private key.

Example Request:

POST /oauth/register
Content-Type: application/json

{
    "redirect_uris": ["https://client.example.com/callback"],
    "token_endpoint_auth_method": "client_secret_basic",
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "client_name": "My Application",
    "client_uri": "https://client.example.com"
}

Example Response:

{
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "client_secret": "dGhpcyBpcyBhIHNlY3JldA==",
    "client_id_issued_at": 1695140656,
    "client_secret_expires_at": 0,
    "redirect_uris": ["https://client.example.com/callback"],
    "token_endpoint_auth_method": "client_secret_basic",
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "client_name": "My Application",
    "client_uri": "https://client.example.com"
}

Response Fields:

Field Description
client_id The unique client identifier assigned by the server.
client_secret The client secret (only for confidential clients).
client_id_issued_at Unix timestamp when the client_id was issued.
client_secret_expires_at Unix timestamp when the client_secret expires. 0 means it does not expire.

All submitted metadata fields are echoed back in the response.

Error Response:

{
    "error": "invalid_client_metadata",
    "error_description": "redirect_uris contains invalid URI: not-a-url"
}

Error Codes:

Error Description
invalid_client_metadata The request contains invalid or malformed metadata.
invalid_software_statement The software statement JWT is invalid or cannot be verified.
unapproved_software_statement The software statement is not signed by an approved issuer.
invalid_token The initial access token is invalid or missing (HTTP 401).

Software Statements

Software statements are signed JWTs that contain pre-approved client metadata. When a software statement is provided, its claims take precedence over request body parameters. This allows software publishers to pre-register their application metadata.

The server can be configured to:

  • Accept any software statement (decode only)
  • Require software statements signed by specific keys (verify signature)
  • Require software statements for all registrations

Configuration

Dynamic client registration must be enabled in the TokenAuthority configuration:

TokenAuthority.configure do |config|
  config.dcr_enabled = true
  config.dcr_allowed_token_endpoint_auth_methods = %w[none client_secret_basic client_secret_post private_key_jwt]
  config.dcr_allowed_grant_types = %w[authorization_code refresh_token]
end

Restricting Client Scopes

When scopes are configured, you can restrict which scopes dynamically registered clients may request:

TokenAuthority.configure do |config|
  # Define all available scopes
  config.scopes = {
    "read" => "Read your data",
    "write" => "Create and modify your data",
    "admin" => "Administrative access"
  }

  # Only allow dynamically registered clients to request read and write
  config.dcr_enabled = true
  config.dcr_allowed_scopes = %w[read write]
end

If dcr_allowed_scopes is not set (nil), dynamically registered clients can request any scope from config.scopes. If set to an empty array, clients cannot register with any scopes.

When a client attempts to register with scopes not in the allowlist, the server returns an invalid_client_metadata error.

References

Clone this wiki locally