mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat(oidc): add RFC 7591 dynamic client registration (#12313)
# Which Problems Are Solved ZITADEL cannot currently be used as an OAuth 2.0 Authorization Server by clients that need to register themselves at runtime: - There is no [OAuth 2.0 Dynamic Client Registration (RFC 7591)](https://datatracker.ietf.org/doc/html/rfc7591) endpoint, and no `registration_endpoint` is advertised in the discovery document. - This blocks [Model Context Protocol (MCP)](https://modelcontextprotocol.io) clients (Claude Desktop, claude.ai, Cursor, the MCP SDKs), whose OAuth 2.1 authorization profile expects an Authorization Server that supports dynamic client registration. They self-register before any user context exists and only then start the authorization code + PKCE flow. # How the Problems Are Solved ## Control plane: instance security settings No feature flag and no runtime config. A `DynamicClientRegistrationSettings` message is nested under the instance `SecuritySettings` in `settings/v2`, next to `embedded_iframe` and `enable_impersonation`: | `enabled` | `allow_unauthenticated` | Behaviour | | --- | --- | --- | | `false` (default) | — | `POST /oauth/v2/register` returns `404`, `registration_endpoint` absent from discovery | | `true` | `false` (default) | Token-gated registration, client homed in the token's organization | | `true` | `true` | Open registration for MCP hosts, client homed in the instance default organization | Both values are carried on `authz.Instance` and loaded with the instance, exactly like `EnableImpersonation`, so the discovery document and the endpoint read them per request without an extra query. `allow_unauthenticated` implies `enabled` in both implementations, so a single setting closes the endpoint. Adding the two columns bumps the projection table to `projections.security_policies3`, following what the impersonation setting did for `security_policies2`. ## Authorization: a dedicated permission, not `project.app.write` - New org-scoped permission **`project.app.register_dynamic`**, granted by default to `ORG_OWNER`, `IAM_OWNER` and `IAM_ORG_MANAGER` — in both `InternalAuthZ` and `SystemAuthZ`, mirroring where `project.app.write` already lives. - New built-in role **`ORG_DYNAMIC_CLIENT_REGISTRAR`** carrying only that permission, so a service user can self-register clients without gaining write access to the organization's existing applications. - Open mode (`allow_unauthenticated = true`) requires no token and no permission: that is the MCP self-register path. - A valid token whose user lacks the permission is answered with **`403 insufficient_scope`** (RFC 6750 §3.1) rather than `401 invalid_token`, so a caller can tell "your token is bad" from "your token may not do this". `verifyAccessToken` also reports invalid tokens as permission denied, so the authorization failure is marked explicitly to keep the two apart. The check runs once, at the endpoint, right after the token is verified and before any state is created, so it also gates the auto-provisioning of the organization's DCR project. The permission itself lives in the command layer next to the others (`Commands.CheckPermissionRegisterDynamicClient`). As the OIDC endpoints are not behind the authorization interceptor, the caller identity is derived from the verified token, the way token exchange does for the impersonating actor. ## Registration itself - **Registered clients are ordinary OIDC applications.** The endpoint reuses the existing application-creation events (`NewApplicationAddedEvent` and `NewOIDCConfigAddedEvent`), so the whole token, authorization and introspection flow keeps working unchanged. They are stored in a dedicated, auto-provisioned project named `ZITADEL DCR` per organization (name lookup), so they do not pollute the `IAMProject` or other projects. - Input is validated with the existing `GetOIDCV1Compliance` rules; `private_key_jwt` and `jwks`/`jwks_uri` are rejected with `invalid_client_metadata`. - **Endpoint wiring without forking the library.** `NewServer` calls `op.RegisterServer` instead of `op.RegisterLegacyServer`, so the new route can be added through the same `op.WithSetRouter` hook as the authorize callback. `op.RegisterLegacyServer` appends a middleware after the caller options, which chi forbids once a route has been registered; `op.NewIssuerInterceptor` reproduces the issuer middleware it would otherwise add. The `/oauth/v2` routing prefix is unchanged. # Additional Changes - **`application_type` inference for custom-scheme redirects** (last commit, isolated). `application_type` is an OpenID Connect member; RFC 7591 does not define it, so clients that only implement RFC 7591 omit it. Native MCP hosts doing so register custom-scheme redirect URIs, which ZITADEL reserves for native applications, and the OIDC default of `web` rejected them with `invalid_redirect_uri`. Inference is limited to custom schemes and to an absent `application_type`, so every request accepted today keeps its current application type and auth method — an `http` loopback redirect still yields a web application. Happy to drop this commit if you would rather keep it out of this PR. - **Docs**: a Dynamic Client Registration integration guide, a `registration_endpoint` section on the OpenID Connect endpoints page, and the new role in the administrators table (plus its description in the Console translations). # Additional Context - Part of #9810. This is **Phase 1 (DCR)**; #12315 adds RFC 7592 and #12316 adds CIMD, both stacked on this branch and rebased once this lands. - Implements the control-plane and authorization model requested in the review of this PR, itself a refinement of the direction in https://github.com/zitadel/zitadel/issues/9810#issuecomment-4891176997. - **Out of scope (deliberately)**: RFC 7592, CIMD, persisting free-form metadata (`logo_uri`, `contacts`, and similar), and `private_key_jwt` / `jwks_uri` (rejected cleanly). No new event types. - Follow-ups explicitly left out, per the review: DCR client lifecycle and richer audit, consent and custom scopes, JWT / `aud` (RFC 8707), and RFC 8414 AS metadata (currently absent from both ZITADEL and `zitadel/oidc`).
This commit is contained in:
@@ -663,6 +663,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Има разрешение за управление на ролеви назначения",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Има разрешение за управление на грантове по проекти",
|
||||
"ORG_PROJECT_CREATOR": "Има разрешение да създава свои собствени проекти и основни настройки",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Има разрешение за динамично регистриране на OAuth 2.0 клиенти",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Има разрешение да се представя за администратор и крайни потребители от организацията",
|
||||
"ORG_END_USER_IMPERSONATOR": "Има разрешение да се представя за крайни потребители от организацията",
|
||||
"ORG_USER_SELF_MANAGER": "Има разрешение да управлява собствените си потребители",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Má oprávnění spravovat přiřazení rolí",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Má oprávnění spravovat pověření projektu",
|
||||
"ORG_PROJECT_CREATOR": "Má oprávnění vytvářet své vlastní projekty a podřízená nastavení",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Má oprávnění dynamicky registrovat klienty OAuth 2.0",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Má oprávnění vydávat se za správce a koncové uživatele z organizace",
|
||||
"ORG_END_USER_IMPERSONATOR": "Má oprávnění vydávat se za koncové uživatele z organizace",
|
||||
"ORG_USER_SELF_MANAGER": "Má oprávnění spravovat svůj vlastní uživatelský účet",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Verfügt über die Berechtigung zum Verwalten von Rollen-Zuweisungen",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Hat die Berechtigung, Projektberechtigungen für externe Organisationen zu verwalten",
|
||||
"ORG_PROJECT_CREATOR": "Hat die Berechtigung, seine eigenen Projekte und dessen Einstellungen zu erstellen",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Hat die Berechtigung, OAuth-2.0-Clients dynamisch zu registrieren",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Hat die Berechtigung, sich als Administrator und Endbenutzer der Organisation auszugeben",
|
||||
"ORG_END_USER_IMPERSONATOR": "Hat die Berechtigung, sich als Endbenutzer der Organisation auszugeben",
|
||||
"ORG_USER_SELF_MANAGER": "Hat die Berechtigung, seinen eigenen Benutzer zu verwalten",
|
||||
|
||||
@@ -665,6 +665,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Has permission to manage role assignments",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Has permission to manage project grants",
|
||||
"ORG_PROJECT_CREATOR": "Has permission to create his own projects and underlying settings",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Has permission to dynamically register OAuth 2.0 clients",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Has permission to impersonate admin and end users from the organization",
|
||||
"ORG_END_USER_IMPERSONATOR": "Has permission to impersonate end users from the organization",
|
||||
"ORG_USER_SELF_MANAGER": "Has permission to manage their own user",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Tiene permiso para gestionar asignaciones de roles",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Tiene permiso para gestionar concesiones de proyecto",
|
||||
"ORG_PROJECT_CREATOR": "Tiene permiso para crear sus propios proyectos y ajustes subyacentes",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Tiene permiso para registrar dinámicamente clientes OAuth 2.0",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Tiene permiso para hacerse pasar por administradores y usuarios finales de la organización",
|
||||
"ORG_END_USER_IMPERSONATOR": "Tiene permiso para hacerse pasar por usuarios finales de la organización",
|
||||
"ORG_USER_SELF_MANAGER": "Tiene permiso para gestionar su propio usuario",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "A le droit de gérer les attributions de rôles",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "A le droit d'octroyer des droits aux projets",
|
||||
"ORG_PROJECT_CREATOR": "A le droit de créer ses propres projets et leurs paramètres sous-jacents.",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "A le droit d'enregistrer dynamiquement des clients OAuth 2.0",
|
||||
"ORG_ADMIN_IMPERSONATOR": "A l'autorisation de se faire passer pour l'administrateur et les utilisateurs finaux de l'organisation",
|
||||
"ORG_END_USER_IMPERSONATOR": "Est autorisé à usurper l'identité des utilisateurs finaux de l'organisation",
|
||||
"ORG_USER_SELF_MANAGER": "A le droit de gérer ses propres utilisateurs",
|
||||
|
||||
@@ -663,6 +663,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Ha l'autorizzazione per gestire le assegnazioni ruoli",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Ha il permesso di gestire le sovvenzioni di progetto (Project Grant)",
|
||||
"ORG_PROJECT_CREATOR": "Ha il permesso di creare propri progetti e le impostazioni sottostanti",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Ha il permesso di registrare dinamicamente client OAuth 2.0",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Ha il permesso per rappresentare l'amministratore e gli utenti finali dell'organizzazione",
|
||||
"ORG_END_USER_IMPERSONATOR": "Ha il permesso per rappresentare gli utenti finali dell'organizzazione",
|
||||
"ORG_USER_SELF_MANAGER": "Ha il permesso per gestire il proprio account utente",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "ロール割り当てを管理する権限を持ちます",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "プロジェクトグラントを管理する権限を持ちます",
|
||||
"ORG_PROJECT_CREATOR": "所有するプロジェクトと配下の設定を作成する権限を持ちます",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "OAuth 2.0 クライアントを動的に登録する権限を持ちます",
|
||||
"ORG_ADMIN_IMPERSONATOR": "組織の管理者およびエンドユーザーになりすます権限がある",
|
||||
"ORG_END_USER_IMPERSONATOR": "組織のエンドユーザーになりすます権限がある",
|
||||
"ORG_USER_SELF_MANAGER": "自身を管理する権限がある",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Има дозвола за менаџирање на доделените улоги",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Има дозвола за менаџирање на овластувања на проекти",
|
||||
"ORG_PROJECT_CREATOR": "Има дозвола за креирање на сопствени проекти и нивни подесувања",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Има дозвола за динамичко регистрирање на OAuth 2.0 клиенти",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Има дозвола да имитира администратор и крајни корисници од организацијата",
|
||||
"ORG_END_USER_IMPERSONATOR": "Има дозвола да ги имитира крајните корисници од организацијата",
|
||||
"ORG_USER_SELF_MANAGER": "Има дозвола за менаџирање на своите корисници",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Heeft toestemming om roltoewijzingen te beheren",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Heeft toestemming om projecttoegang te beheren",
|
||||
"ORG_PROJECT_CREATOR": "Heeft toestemming om zijn eigen projecten en onderliggende instellingen aan te maken",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Heeft toestemming om OAuth 2.0-clients dynamisch te registreren",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Heeft toestemming om de beheerder en eindgebruikers van de organisatie na te bootsen",
|
||||
"ORG_END_USER_IMPERSONATOR": "Heeft toestemming om eindgebruikers van de organisatie na te bootsen",
|
||||
"ORG_USER_SELF_MANAGER": "Heeft toestemming om zijn eigen gebruiker te beheren",
|
||||
|
||||
@@ -663,6 +663,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Ma uprawnienia do zarządzania przypisaniami ról",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Ma uprawnienie do zarządzania uprawnieniami projektu",
|
||||
"ORG_PROJECT_CREATOR": "Ma uprawnienie do tworzenia własnych projektów i podstawowych ustawień",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Ma uprawnienie do dynamicznego rejestrowania klientów OAuth 2.0",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Ma uprawnienia do podszywania się pod administratora i użytkowników końcowych z organizacji",
|
||||
"ORG_END_USER_IMPERSONATOR": "Ma uprawnienia do podszywania się pod użytkowników końcowych z organizacji",
|
||||
"ORG_USER_SELF_MANAGER": "Ma uprawnienie do zarządzania swoim własnym kontem użytkownika",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Tem permissão para gerenciar atribuições de funções",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Tem permissão para gerenciar concessões de projetos",
|
||||
"ORG_PROJECT_CREATOR": "Tem permissão para criar seus próprios projetos e configurações subjacentes",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Tem permissão para registrar dinamicamente clientes OAuth 2.0",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Tem permissão para se passar por administradores e usuários finais da organização",
|
||||
"ORG_END_USER_IMPERSONATOR": "Tem permissão para se passar por usuários finais da organização",
|
||||
"ORG_USER_SELF_MANAGER": "Tem permissão para gerenciar seu próprio usuário",
|
||||
|
||||
@@ -668,6 +668,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Имеет разрешение на управление назначениями ролей",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Имеет разрешение на управление допусками проекта",
|
||||
"ORG_PROJECT_CREATOR": "Имеет разрешение на создание собственных проектов и базовых настроек",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "Имеет разрешение на динамическую регистрацию клиентов OAuth 2.0",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Имеет разрешение выдавать себя за администратора и конечных пользователей организации",
|
||||
"ORG_END_USER_IMPERSONATOR": "Имеет разрешение выдавать себя за конечных пользователей организации",
|
||||
"ORG_USER_SELF_MANAGER": "Имеет разрешение на управление своим собственным пользователем",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "Rol atamalarını yönetme izni var",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "Proje yetkilerini yönetme iznine sahiptir",
|
||||
"ORG_PROJECT_CREATOR": "Kendi projelerini ve altyapı ayarlarını oluşturma iznine sahiptir",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "OAuth 2.0 istemcilerini dinamik olarak kaydetme iznine sahiptir",
|
||||
"ORG_ADMIN_IMPERSONATOR": "Organizasyondan admin ve son kullanıcıların kimliğine bürünme iznine sahiptir",
|
||||
"ORG_END_USER_IMPERSONATOR": "Organizasyondan son kullanıcıların kimliğine bürünme iznine sahiptir",
|
||||
"ORG_USER_SELF_MANAGER": "Kendi kullanıcısını yönetme iznine sahiptir",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"ORG_USER_PERMISSION_EDITOR": "具有管理角色分配的权限",
|
||||
"ORG_PROJECT_PERMISSION_EDITOR": "有权管理项目授权",
|
||||
"ORG_PROJECT_CREATOR": "有权创建自己的项目和基础设置",
|
||||
"ORG_DYNAMIC_CLIENT_REGISTRAR": "有权动态注册 OAuth 2.0 客户端",
|
||||
"ORG_ADMIN_IMPERSONATOR": "有权模拟组织的管理员和最终用户",
|
||||
"ORG_END_USER_IMPERSONATOR": "有权模拟组织的最终用户",
|
||||
"ORG_USER_SELF_MANAGER": "有权管理自己的用户",
|
||||
|
||||
Reference in New Issue
Block a user