Dynamic Client Registration

Copy Markdown

Lockspire supports Dynamic Client Registration (DCR) via RFC 7591 and RFC 7592. This allows external ecosystem partners to register their OAuth/OIDC clients programmatically without operator intervention, provided they have been issued an Initial Access Token (IAT).

For the canonical public support contract, see docs/supported-surface.md. For the shipped symmetric JWT direct-client slice specifically, see docs/client-secret-jwt-host-guide.md.

Operator Setup

DCR is controlled by Lockspire's server policy. Out of the box, DCR might be disabled to ensure secure defaults. To enable it:

  1. Navigate to the Server Policies section in your Lockspire Admin UI.
  2. Ensure the Global Registration Policy is set to initial_access_token. This strict setting ensures that only partners explicitly given an IAT can register. Open registration is deliberately not supported to prevent uncontrolled growth and spam.
  3. Configure your host application router to rate-limit the Lockspire Registration endpoints. Lockspire does not provide built-in rate limiting. It is your responsibility to wrap the Lockspire routes (or at least /register) in an Elixir Plug that performs appropriate rate limiting.

Initial Access Token (IAT) Lifecycle

As an operator, you control who can register by issuing Initial Access Tokens (IATs):

  1. Minting: In the Lockspire Admin UI under Initial Access Tokens, you can create a new IAT for a specific partner. You can assign a name to track who you gave it to, and set an expiration time if you choose.
  2. Distribution: Once created, you must securely transmit the plain text secret to the partner. Lockspire will only display it once.
  3. Usage: The partner will include this token as a Bearer token in the Authorization header of their POST /register request.
  4. Revocation/Redemption: IATs can be configured to be multi-use or explicitly revoked via the admin interface if a partner's access needs to be terminated.

Partner Integration

Once a partner has an Initial Access Token, they can integrate with your Lockspire provider.

Client Registration

To register a new client, send a POST request to the registration endpoint (e.g., https://your-domain.com/oauth/register) with your IAT as the bearer token.

Request:

POST /oauth/register HTTP/1.1
Host: your-domain.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer <INITIAL_ACCESS_TOKEN>

{
  "client_name": "My Cool App",
  "redirect_uris": [
    "https://app.example.com/callback"
  ]
}

Response:

A successful response (HTTP 201 Created) will return the newly minted client details, along with a registration_access_token and registration_client_uri.

{
  "client_id": "cli_abc123",
  "client_secret": "sec_def456",
  "client_id_issued_at": 1610000000,
  "client_secret_expires_at": 0,
  "client_name": "My Cool App",
  "redirect_uris": [
    "https://app.example.com/callback"
  ],
  "token_endpoint_auth_method": "client_secret_basic",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "registration_access_token": "rat_xyz789",
  "registration_client_uri": "https://your-domain.com/oauth/register/cli_abc123"
}

Registration Access Token (RAT)

The registration_access_token allows the partner to manage the client they just created. They must securely store both their client credentials (client_id, client_secret) and the registration_access_token.

Read, Update, and Delete

Partners can read, update, or delete their client via the registration_client_uri using the RAT.

  • Read (GET): GET /oauth/register/cli_abc123 with Authorization: Bearer <RAT>
  • Update (PUT): PUT /oauth/register/cli_abc123 with Authorization: Bearer <RAT> and the full JSON representation of the updated client. This will rotate both the client_secret and the registration_access_token.
  • Delete (DELETE): DELETE /oauth/register/cli_abc123 with Authorization: Bearer <RAT>

client_secret_jwt metadata shape

Lockspire supports client_secret_jwt only as a narrow confidential-client direct-client slice. When a client chooses that mode, send the auth method and signing algorithm together:

  • token_endpoint_auth_method=client_secret_jwt
  • token_endpoint_auth_signing_alg=HS256

This shipped slice is confidential-client only, uses issuer-string aud, and does not extend to POST /par, HS384, HS512, FAPI, or mTLS equivalence claims.

Create with explicit client_secret_jwt metadata:

POST /oauth/register HTTP/1.1
Host: your-domain.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer <INITIAL_ACCESS_TOKEN>

{
  "client_name": "Partner direct client",
  "redirect_uris": [
    "https://app.example.com/callback"
  ],
  "token_endpoint_auth_method": "client_secret_jwt",
  "token_endpoint_auth_signing_alg": "HS256"
}
{
  "client_id": "cli_abc123",
  "client_secret": "sec_def456",
  "client_id_issued_at": 1610000000,
  "client_secret_expires_at": 0,
  "client_name": "Partner direct client",
  "redirect_uris": [
    "https://app.example.com/callback"
  ],
  "token_endpoint_auth_method": "client_secret_jwt",
  "token_endpoint_auth_signing_alg": "HS256",
  "registration_access_token": "rat_xyz789",
  "registration_client_uri": "https://your-domain.com/oauth/register/cli_abc123"
}

Logout propagation metadata lifecycle

Lockspire's DCR surface can create, read, and update the four existing logout propagation metadata fields:

  • backchannel_logout_uri
  • backchannel_logout_session_required
  • frontchannel_logout_uri
  • frontchannel_logout_session_required

These settings control logout propagation to the relying party. They are separate from post-logout redirect URIs, which are browser destinations after RP-initiated logout.

These metadata fields manage the existing shipped logout runtime; they do not create a second logout system. After the host app clears its own browser session and returns to /end_session/complete, Lockspire persists propagation intent, enqueues back-channel delivery, and renders front-channel cleanup as best effort browser choreography only.

Back-channel logout is the durable server-to-server path. Front-channel logout is best effort only and should be treated as browser choreography rather than proof of remote success.

Create with logout propagation metadata:

POST /oauth/register HTTP/1.1
Host: your-domain.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer <INITIAL_ACCESS_TOKEN>

{
  "client_name": "My Cool App",
  "redirect_uris": [
    "https://app.example.com/callback"
  ],
  "backchannel_logout_uri": "https://rp.example.test/backchannel-logout",
  "backchannel_logout_session_required": true,
  "frontchannel_logout_uri": "https://app.example.test/frontchannel-logout",
  "frontchannel_logout_session_required": true
}
{
  "client_id": "cli_abc123",
  "client_secret": "sec_def456",
  "client_name": "My Cool App",
  "redirect_uris": [
    "https://app.example.com/callback"
  ],
  "backchannel_logout_uri": "https://rp.example.test/backchannel-logout",
  "backchannel_logout_session_required": true,
  "frontchannel_logout_uri": "https://app.example.test/frontchannel-logout",
  "frontchannel_logout_session_required": true,
  "registration_access_token": "rat_xyz789",
  "registration_client_uri": "https://your-domain.com/oauth/register/cli_abc123"
}

Read the stored values:

GET /oauth/register/cli_abc123 HTTP/1.1
Host: your-domain.com
Accept: application/json
Authorization: Bearer <RAT>

The management response returns the same persisted logout propagation fields so the relying party can confirm the server's stored state.

Update with RFC 7592 full-replace semantics:

PUT /oauth/register/cli_abc123 HTTP/1.1
Host: your-domain.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer <RAT>

{
  "client_name": "Updated logout fixture client",
  "redirect_uris": [
    "https://app.example.com/callback"
  ],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "client_secret_basic",
  "scope": "openid profile",
  "backchannel_logout_uri": "https://rp.example.test/replaced-backchannel-logout",
  "backchannel_logout_session_required": false,
  "frontchannel_logout_uri": "https://app.example.test/replaced-frontchannel-logout",
  "frontchannel_logout_session_required": false
}

RFC 7592 PUT is full-replace, not patch. If logout propagation fields are omitted from an update, the omitted values clear and Lockspire persists nil / false for those fields on the stored client.

The same full-replace rule applies to auth-method metadata. If a client stays on client_secret_jwt, send both token_endpoint_auth_method and token_endpoint_auth_signing_alg together so the stored HS256 truth remains coherent. If an update changes auth method away from client_secret_jwt, omit the old symmetric JWT signing-alg expectation and let the new method's metadata define the replacement state.

The returned registration_access_token replaces the old RAT immediately. Any returned client_secret replaces the old client credential immediately.

{
  "client_id": "cli_abc123",
  "client_secret": "sec_rotated789",
  "client_name": "Updated logout fixture client",
  "redirect_uris": [
    "https://app.example.com/callback"
  ],
  "backchannel_logout_uri": "https://rp.example.test/replaced-backchannel-logout",
  "backchannel_logout_session_required": false,
  "frontchannel_logout_uri": "https://app.example.test/replaced-frontchannel-logout",
  "frontchannel_logout_session_required": false,
  "registration_access_token": "rat_rotated456",
  "registration_client_uri": "https://your-domain.com/oauth/register/cli_abc123"
}

Out of Scope

To ensure a secure and explicit deployment model, the following Dynamic Client Registration features are not supported:

  • Open Registration
  • Software Statements (RFC 7591 ยง2.3)
  • External-IdP federation and FAPI bundles
  • JAR-04 encryption
  • jwks_uri outbound fetch