Dynamic Client Registration
Dynamic client registration lets an application register itself as an OAuth client at LumoAuth with one API call, instead of an administrator creating it in the portal. Use it for marketplaces, CI pipelines, and MCP clients that discover LumoAuth at run time. The endpoint is advertised as registration_endpoint in the discovery document; terms are explained in the glossary.
POST /orgs/{orgId}/api/v1/connect/register
An administrator must switch on Dynamic client registration under Security → Sign-in settings in the portal (API: dynamic_client_registration_enabled in the sign-in settings) before clients can self-register. While it is off, every request is refused with 403 access_denied; an API key with the admin:clients:register scope can still register clients.
Authentication
By default, registration requests must be authenticated. Choose one of:
- Initial Access Token — pass
Authorization: Bearer <initial_access_token>. This is required by default (require_initial_access_tokendefaults totrue), so enabling DCR alone does not open an anonymous registration endpoint. - Admin API key — a key with the
admin:clients:registerscope bypasses the Initial Access Token gate.
Initial access tokens are configured in the organization's authentication settings (initial_access_tokens, a list). They are stored as SHA-256 hashes and returned redacted, so keep your own copy of each token when you create it.
Fully anonymous registration (no Authorization header) requires an administrator to both enable DCR and explicitly set require_initial_access_token to false. Without a valid token, an anonymous request is rejected with 401 access_denied (Initial Access Token required).
When to Use This
Dynamic registration is useful for:
- Marketplaces — third-party apps can register themselves.
- CI/CD — automatically provision clients during deployment.
- Mobile apps — register a unique client per device.
- Multi-organization platforms — create clients programmatically.
Request
| Parameter | Required | Description |
|---|---|---|
client_name | No | Human-readable name for the application. Control characters, <, >, backticks and ${ are stripped and the name is cut to 100 characters; an empty result becomes Unnamed Client. |
redirect_uris | Yes | Array of allowed redirect URIs (validated per application_type, see Redirect URI rules) |
application_type | No | web (default) or native. Web clients must register https redirect URIs; native clients may use custom URI schemes (com.example.app:/callback) or http loopback URIs (http://127.0.0.1:*, http://localhost, http://[::1]). |
post_logout_redirect_uris | No | Same scheme rules as redirect_uris; entries with a forbidden scheme are dropped. |
client_uri, logo_uri, policy_uri, tos_uri | No | Must be http or https URLs without credentials; they are rendered on the consent page. |
grant_types | No | Array of grant types. Default: derived from response_types, which for code is ["authorization_code", "refresh_token"]. A client registered without refresh_token receives no refresh tokens. |
response_types | No | Array of response types (default: ["code"]) |
scope | No | Space-separated list of requested scopes |
token_endpoint_auth_method | No | client_secret_basic (default), client_secret_post, private_key_jwt (requires jwks or jwks_uri), tls_client_auth, or none. client_secret_jwt is rejected — secrets are stored hashed, so HMAC assertions cannot be verified. |
jwks / jwks_uri | For private_key_jwt / JAR | The client's public signing keys (mutually exclusive). Also used to verify signed request objects. |
request_object_signing_alg | No | Asymmetric JWS algorithm the client will sign request objects with (RS*, PS*, ES*); none and HS* are rejected. |
authorization_signed_response_alg | No | Validated against the supported JARM algorithms, but not persisted — responses are signed with the organization's signing key. |
frontchannel_logout_uri, backchannel_logout_uri | No | Logout notification endpoints; see Logout. |
audience_uris | No | The API identifiers the client may request tokens for; see Resource indicators. |
Encrypted-token metadata is not supported: id_token_encrypted_response_alg/enc, userinfo_encrypted_response_alg/enc, request_object_encryption_alg/enc and authorization_encrypted_response_alg/enc are rejected with invalid_client_metadata ("encrypted responses are not supported").
Redirect URI rules
Every redirect URI is checked twice: first against the scheme policy shared with the portal and the admin API, then against the application_type rules.
| Rule | Result when violated |
|---|---|
Scheme must be https, http, or a private-use scheme; javascript:, data:, vbscript:, blob:, file:, about: and URIs containing control characters or quotes are refused | 400 invalid_redirect_uri ("forbidden scheme or contains invalid characters") |
application_type: web requires https | 400 invalid_redirect_uri ("Web Client redirect_uris must use the https scheme") |
application_type: native allows http only on localhost, 127.0.0.1, [::1], plus private-use schemes | 400 invalid_redirect_uri |
Presentation URIs must be http(s) | 400 invalid_client_metadata ("... must use http or https scheme") |
At authorization time the redirect_uri you send must match one of these entries exactly, port included.
Dynamic client registration defines the registration request and response, and OAuth for native apps defines what a native app may use as a redirect target (loopback http or a private-use scheme). To comply, register the exact URIs your app will send and set application_type: native for anything that is not a server-rendered web app.
Example Request
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/connect/register \
-H "Authorization: Bearer YOUR_INITIAL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_name": "My Mobile App",
"application_type": "native",
"redirect_uris": [
"com.example.app:/callback",
"http://127.0.0.1:49152/callback"
],
"grant_types": ["authorization_code", "refresh_token"],
"scope": "openid profile email"
}'
Response
{
"client_id": "dyn_abc123def456",
"client_secret": "cs_live_xxxxxxxxxxxxxxxxxxxxxxxx",
"client_id_issued_at": 1704063600,
"client_secret_expires_at": 0,
"client_name": "My Mobile App",
"application_type": "native",
"redirect_uris": [
"com.example.app:/callback",
"http://127.0.0.1:49152/callback"
],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "openid profile email",
"token_endpoint_auth_method": "client_secret_basic",
"registration_access_token": "rat_xxxxxxxxxxxxxxxxxxxxxxxx",
"registration_client_uri": "https://app.lumoauth.dev/orgs/acme-corp/api/v1/connect/register/dyn_abc123def456"
}
The client_secret is only returned once during registration. It is stored hashed and cannot be retrieved later, not even by an administrator; rotate it to get a new one.
Response Fields
| Field | Description |
|---|---|
client_id | The unique identifier for the new client |
client_secret | The secret for confidential clients (null for public clients) |
client_id_issued_at | Unix timestamp when the client was created |
client_secret_expires_at | When the secret expires (0 = never) |
registration_access_token | Bearer token for reading the registration back at registration_client_uri; shown once |
registration_client_uri | The client configuration endpoint for this client |
Reading a Registered Client
Once registered, a client can read its own registration back from the client configuration endpoint, authenticating with the registration_access_token from the registration response:
curl https://app.lumoauth.dev/orgs/acme-corp/api/v1/connect/register/dyn_abc123def456 \
-H "Authorization: Bearer rat_xxxxxxxxxxxxxxxxxxxxxxxx"
The response is the registered metadata (never the client_secret or the registration access token). A missing or wrong token gets 401 invalid_token. The client configuration endpoint is read-only: updating or deleting a registration is done by an administrator through the OAuth Clients admin API or the portal.
The registration_access_token is bound to the authorization-server issuer it was minted under. If a client registers via the org-path issuer (https://app.lumoauth.dev/orgs/{orgId}/api/v1) the token cannot be replayed against the same organization's custom-domain issuer (https://auth.acme.com), and vice versa.
Errors
| HTTP | error | Cause |
|---|---|---|
| 400 | invalid_request | Body is not valid JSON, or the organization is unknown |
| 400 | invalid_redirect_uri | A redirect URI breaks the rules above |
| 400 | invalid_client_metadata | Unsupported metadata, for example client_secret_jwt, an encryption algorithm, or a non-http(s) presentation URI |
| 401 | access_denied | No valid initial access token (Initial Access Token required) |
| 403 | access_denied | Dynamic client registration is not enabled for the organization |
| 429 | too_many_requests | More than 10 registrations per minute |
Common mistakes
invalid_redirect_uriforhttp://localhost:3000/callback. The defaultapplication_typeisweb, which requireshttps. Send"application_type": "native"for loopback redirects.invalid_client_metadataforlogo_uri. Onlyhttp(s)URLs without embedded credentials are accepted, and the host must be publicly reachable.- An initial access token of 64 hex characters never works. A 64-hex value in
initial_access_tokensis treated as an already-hashed token. Use a token with another format (for example a random base64url string). - Registered name looks different. Markup characters and control characters were stripped from
client_name, and it was cut to 100 characters. - No
refresh_tokenin token responses. You sentgrant_typeswithoutrefresh_token. - Trying to read the secret back later. It is hashed. Rotate it through the admin API or the portal.
Related
- OAuth Clients — admin API for clients
- Scopes
Standards used on this page
| Plain name | Standard |
|---|---|
| Dynamic client registration | RFC 7591 and RFC 7592 |
| Discovery document | RFC 8414 and OpenID Connect Discovery |
| OAuth for native apps | RFC 8252 |
| Resource indicators | RFC 8707 |
| Signed authorization requests (JAR) | RFC 9101 |
| Signed authorization responses (JARM) | OpenID JWT Secured Authorization Response Mode |
| Logout | OpenID Connect Front-Channel Logout and Back-Channel Logout |
See the standards reference for what each one covers and how LumoAuth uses it.