Skip to main content

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

Configuration Required

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_token defaults to true), so enabling DCR alone does not open an anonymous registration endpoint.
  • Admin API key — a key with the admin:clients:register scope 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​

ParameterRequiredDescription
client_nameNoHuman-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_urisYesArray of allowed redirect URIs (validated per application_type, see Redirect URI rules)
application_typeNoweb (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_urisNoSame scheme rules as redirect_uris; entries with a forbidden scheme are dropped.
client_uri, logo_uri, policy_uri, tos_uriNoMust be http or https URLs without credentials; they are rendered on the consent page.
grant_typesNoArray 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_typesNoArray of response types (default: ["code"])
scopeNoSpace-separated list of requested scopes
token_endpoint_auth_methodNoclient_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_uriFor private_key_jwt / JARThe client's public signing keys (mutually exclusive). Also used to verify signed request objects.
request_object_signing_algNoAsymmetric JWS algorithm the client will sign request objects with (RS*, PS*, ES*); none and HS* are rejected.
authorization_signed_response_algNoValidated against the supported JARM algorithms, but not persisted — responses are signed with the organization's signing key.
frontchannel_logout_uri, backchannel_logout_uriNoLogout notification endpoints; see Logout.
audience_urisNoThe 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.

RuleResult 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 refused400 invalid_redirect_uri ("forbidden scheme or contains invalid characters")
application_type: web requires https400 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 schemes400 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.

Standard

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"
}
Store Your Secret

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​

FieldDescription
client_idThe unique identifier for the new client
client_secretThe secret for confidential clients (null for public clients)
client_id_issued_atUnix timestamp when the client was created
client_secret_expires_atWhen the secret expires (0 = never)
registration_access_tokenBearer token for reading the registration back at registration_client_uri; shown once
registration_client_uriThe 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.

Issuer binding (SEP-2352)

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​

HTTPerrorCause
400invalid_requestBody is not valid JSON, or the organization is unknown
400invalid_redirect_uriA redirect URI breaks the rules above
400invalid_client_metadataUnsupported metadata, for example client_secret_jwt, an encryption algorithm, or a non-http(s) presentation URI
401access_deniedNo valid initial access token (Initial Access Token required)
403access_deniedDynamic client registration is not enabled for the organization
429too_many_requestsMore than 10 registrations per minute

Common mistakes​

  • invalid_redirect_uri for http://localhost:3000/callback. The default application_type is web, which requires https. Send "application_type": "native" for loopback redirects.
  • invalid_client_metadata for logo_uri. Only http(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_tokens is 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_token in token responses. You sent grant_types without refresh_token.
  • Trying to read the secret back later. It is hashed. Rotate it through the admin API or the portal.

Standards used on this page​

Plain nameStandard
Dynamic client registrationRFC 7591 and RFC 7592
Discovery documentRFC 8414 and OpenID Connect Discovery
OAuth for native appsRFC 8252
Resource indicatorsRFC 8707
Signed authorization requests (JAR)RFC 9101
Signed authorization responses (JARM)OpenID JWT Secured Authorization Response Mode
LogoutOpenID Connect Front-Channel Logout and Back-Channel Logout

See the standards reference for what each one covers and how LumoAuth uses it.