Dynamic Client Registration
Copy MarkdownLockspire 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:
- Navigate to the Server Policies section in your Lockspire Admin UI.
- 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. - 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 ElixirPlugthat performs appropriate rate limiting.
Initial Access Token (IAT) Lifecycle
As an operator, you control who can register by issuing Initial Access Tokens (IATs):
- 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.
- Distribution: Once created, you must securely transmit the plain text secret to the partner. Lockspire will only display it once.
- Usage: The partner will include this token as a
Bearertoken in theAuthorizationheader of theirPOST /registerrequest. - 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_abc123withAuthorization: Bearer <RAT> - Update (PUT):
PUT /oauth/register/cli_abc123withAuthorization: Bearer <RAT>and the full JSON representation of the updated client. This will rotate both theclient_secretand theregistration_access_token. - Delete (DELETE):
DELETE /oauth/register/cli_abc123withAuthorization: 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_jwttoken_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_uribackchannel_logout_session_requiredfrontchannel_logout_urifrontchannel_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_urioutbound fetch