---
title: OAuth 2.0 Dynamic Client Registration
description: "Let clients register themselves as OIDC applications at runtime using RFC 7591, for example for Model Context Protocol (MCP) clients."
sidebar_label: Dynamic Client Registration
---
ZITADEL implements [OAuth 2.0 Dynamic Client Registration (RFC 7591)](https://datatracker.ietf.org/doc/html/rfc7591).
It exposes a [`registration_endpoint`](/apis/openidoauth/endpoints#registration-endpoint) that lets clients register
themselves as OIDC applications at runtime, instead of being created upfront in the Management Console or through the management API.
This is required by clients that self-register before any user context exists, most notably
[Model Context Protocol (MCP)](https://modelcontextprotocol.io) clients such as Claude Desktop, claude.ai and the MCP SDKs.
Dynamic client registration is disabled by default. Only enable it if you understand the trade-offs described in
[Registration modes](#registration-modes) below.
## Enable dynamic client registration
The endpoint is only served and advertised as `registration_endpoint` in the
[discovery document](/apis/openidoauth/endpoints#openid-connect-10-discovery) when it is enabled in the instance's
security settings.
Enable it through the [settings API](/apis/resources/settings_service_v2/settings-service-set-security-settings):
```bash
curl --request PUT \
--url ${CUSTOM_DOMAIN}/v2/settings/security \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "dynamicClientRegistration": { "enabled": true } }'
```
## Registration modes
The flow that MCP clients follow expects an open, unauthenticated registration endpoint, because the client registers
before any user has authenticated. As this is a sensitive capability, ZITADEL supports two modes, selected through the
same security settings. Both require `enabled` above.
| Mode | Setting | Authorization | Organization of the registered client |
| --- | --- | --- | --- |
| Token (default) | `dynamicClientRegistration.allowUnauthenticated = false` | An access token must be sent as `Authorization: Bearer` header, and its user must hold the `project.app.register_dynamic` permission in the token's organization. | The token's organization. |
| Open registration | `dynamicClientRegistration.allowUnauthenticated = true` | None. Required for the MCP flow. | The instance's default organization. |
### Who may register in token mode
The `project.app.register_dynamic` permission is granted by default to `ORG_OWNER`, `IAM_OWNER` and `IAM_ORG_MANAGER`.
For service users that should do nothing but register clients, assign the dedicated
[`ORG_DYNAMIC_CLIENT_REGISTRAR`](/guides/manage/console/administrators) role instead. It carries this single permission,
so such a user can self-register clients without gaining write access to your existing applications.
Whenever an `Authorization: Bearer` header is present it is treated as a registration token, even with open registration
enabled. An invalid or expired token is rejected with `401 Unauthorized` and a valid token without the permission with
`403 Forbidden`; neither falls back to anonymous registration.
In both modes the registered clients are stored in a dedicated, auto-provisioned project named `ZITADEL DCR` in the
respective organization, so they do not interfere with your other projects. When open registration is enabled, protect
the endpoint with the existing instance rate limiting and only allow the redirect URI schemes you trust.
## Register a client
Send the [client metadata](#supported-metadata) as JSON to the `registration_endpoint`:
```bash
curl --request POST \
--url ${CUSTOM_DOMAIN}/oauth/v2/register \
--header 'Content-Type: application/json' \
--data '{
"client_name": "My MCP Client",
"redirect_uris": ["https://client.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}'
```
A successful registration returns `HTTP 201` with the client information response:
```json
{
"client_id": "340396026519785524",
"client_id_issued_at": 1718000000,
"client_name": "My MCP Client",
"redirect_uris": ["https://client.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
```
For confidential clients (`token_endpoint_auth_method` of `client_secret_basic` or `client_secret_post`) the response
also contains a `client_secret` and `client_secret_expires_at` (`0`, secrets do not expire).
The returned `client_id` can be used immediately to start an
[authorization code flow](/guides/integrate/login/oidc/login-users). Public clients (`token_endpoint_auth_method` of
`none`) must use [PKCE](https://datatracker.ietf.org/doc/html/rfc7636).
## Supported metadata
| Member | Notes |
| --- | --- |
| `redirect_uris` | Required. Validated against the same rules as applications created in the Management Console. |
| `token_endpoint_auth_method` | `client_secret_basic`, `client_secret_post` or `none`. Defaults to `none` for native applications and `client_secret_basic` for web applications. |
| `grant_types` | Defaults to `["authorization_code"]`. `refresh_token`, `implicit`, `urn:ietf:params:oauth:grant-type:device_code` and `urn:ietf:params:oauth:grant-type:token-exchange` are supported. |
| `response_types` | Defaults to `["code"]`. `id_token` and `id_token token` are supported. |
| `application_type` | `web` or `native`. When omitted, defaults to `web`, except for redirect URIs with a custom scheme (`myapp://callback`), which imply `native`. Clients that only implement RFC 7591 do not send this member. |
| `client_name` | Optional, human-readable name. |
| `post_logout_redirect_uris` | Optional. |
Metadata errors are returned as defined in [RFC 7591 §3.2.2](https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.2),
for example `invalid_redirect_uri` or `invalid_client_metadata`, with HTTP status `400`. Authorization failures use the
[RFC 6750 §3.1](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1) codes `invalid_token` (`401`) and
`insufficient_scope` (`403`).
## Limitations
This first version intentionally keeps a small scope:
- Free-form metadata such as `logo_uri`, `client_uri` or `contacts` is accepted but not persisted.
- The `private_key_jwt` authentication method and the `jwks`/`jwks_uri` members are rejected with `invalid_client_metadata`.
- Reading, updating and deleting a registration ([RFC 7592](https://datatracker.ietf.org/doc/html/rfc7592)) is not yet supported.
- Dynamically registered clients share the audience of the `ZITADEL DCR` project. A JWT access token issued to one of
them carries every client ID registered in that project, plus the project ID, in its `aud` claim. The `resource`
parameter ([RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)) is accepted on the authorization code flow but
ignored, so it does not narrow `aud`. Do not rely on `aud` alone to tell one dynamically registered client from
another; validate the `client_id` or `azp` claim instead.