跳至正文
Clerk CIMD:Client ID Metadata Document 原理、场景与 MCP

Clerk CIMD:Client ID Metadata Document 原理、场景与 MCP

AI 参与说明(Agent:Grok Bot / yindongliang.com):本文根据 Clerk CIMD GA changelog(标注日期 2026-09-21)与官方指南 Manage OAuth clients with Client ID Metadata Documents 整理,属于产品调研,不构成对任一实例的配置承诺。文档措辞若仍带 Beta,以 GA changelog 为准。

资料复核(2026-10-21,Agent:Cursor):对照 CIMD 指南、How Clerk implements OAuth、Connect MCP-compatible clients、useOAuthConsent() 与 custom OAuth consent page 复核调用顺序、同意页提交方式和 CLI 字段名。检索先经 Context7 /clerk/clerk-docs,结论以当日打开的 clerk.com 原文为准。运行记录:模型 grok-4.7-high-fast,执行入口 Cursor Cloud;reasoning effort 未单独取得运行记录。复核不表示任何具体实例已经打开 Publish CIMD。

修订(2026-10-22,Agent:Cursor):去掉正文里的内部产品称呼;原对照节改为「落地时注意」,三条 Clerk 文档差异保留。slug 与其余技术正文未改。运行记录:模型 grok-4.7-high-fast,执行入口 Cursor Cloud;reasoning effort 未单独取得运行记录。

先给结论: Client ID Metadata Document(CIMD)让 OAuth 公共客户端用一个 HTTPS URL 充当 client_id;授权服务器去该 URL 拉取 JSON 元数据,校验名称与 redirect_uris,从而在不预发 client secret、也不必走 Dynamic Client Registration(DCR)写端点的情况下识别客户端。Clerk 已于 2026-09-21 起对所有应用 GA,默认关闭;开启后适合 MCP 与其他需要稳定身份的公共 OAuth 客户端。

术语含义
CIMDClient ID Metadata Document:以 HTTPS URL 为 client_id,URL 上托管客户端元数据 JSON
传统 client_id在授权服务器预注册后得到的不透明字符串(常配 client_secret)
DCRDynamic Client Registration:客户端运行时向授权服务器注册并拿到凭据
公共客户端无法安全保管密钥的客户端(浏览器、桌面、CLI、许多 MCP 宿主)
PKCEProof Key for Code Exchange;CIMD 在 Clerk 上强制 S256
Client admissionClerk 控制「未知 CIMD 客户端能否连入」的策略
Frontend APIClerk 实例上的授权服务器域名;发现、授权和换 token 都在这里
useOAuthConsent自定义同意页用来读取应用名称、scopes 与 redirect 信息的 React hook
buildConsentActionUrl按 clientId 生成把同意结果 POST 回 Frontend API 的方法

要解决什么问题

传统 OAuth 第一方集成:在 Dashboard 创建应用 → 得到 client_id(+ 机密客户端的 secret)→ 把 redirect 白名单写死。这对「你控制的」应用很合适。

MCP 与一批公共客户端的麻烦在于:

  • 客户端种类多、发布方分散,难以为每一个宿主事先发一枚不透明 client_id。
  • 公共客户端不能安全持有 client_secret。
  • Dynamic Client Registration 能「自助注册」,但会暴露未认证的客户端注册写接口,运维与滥用面更大。Clerk 文档明确建议:客户端若支持 CIMD,优先 CIMD,仅在对方不支持时再开 DCR。

CIMD 的折中是:身份写在客户端自己可托管的 HTTPS 文档里;授权服务器按需拉取并校验,而不是让客户端向你「注册出一个新 ID」。

原理:URL 即身份

兼容客户端把 client_id 设成元数据文档的 HTTPS URL(须带 path)。Clerk 拉取该 URL 上的 JSON,并要求:

  1. 文档内 client_id 与被拉取的 URL 完全一致
  2. 有 client_name,且至少一条 redirect_uris
  3. 授权请求里的 redirect_uri 精确匹配文档中的某一项
  4. 客户端必须是公共客户端:token_endpoint_auth_method 为 none;含 client secret 或选择基于 secret 的 token 端点认证方式会被拒绝

最小示意:

json
{
  "client_id": "https://client.example.com/oauth/client-metadata.json",
  "client_name": "Example client",
  "redirect_uris": ["https://client.example.com/oauth/callback"],
  "token_endpoint_auth_method": "none"
}

Clerk 侧行为要点:

  • 一律按公共客户端处理
  • 强制 PKCE(S256)
  • 始终展示 OAuth consent(同意页须可达:Account Portal 或自建 consent 路由)
  • 权限范围以 Dashboard 配置为准;元数据不能自行扩大 scopes;CIMD 客户端总会带上 offline_access

开启「Publish CIMD support」后,授权服务器元数据会发布 client_id_metadata_document_supported,兼容客户端可据此选择 CIMD 而非 DCR。关闭时,元数据里没有这个字段。

调用流程

一次 CIMD 授权按下面的顺序走。Client admission 先决定这个 URL 能不能开始;Explicitly allowed 的客户端不受「未知客户端」策略阻挡。下面假设客户端已被允许。

sequenceDiagram
    participant Client as OAuth Client
    participant FAPI as Frontend API
    participant CIMD as CIMD URL
    participant User as User
    participant Resource as Resource

    Client->>FAPI: GET /.well-known/oauth-authorization-server
    FAPI-->>Client: 端点;开启时含 client_id_metadata_document_supported
    Client->>FAPI: GET /oauth/authorize,URL client_id 与 PKCE S256
    FAPI->>CIMD: 拉取元数据 JSON
    alt 拉取失败或校验不通过
        FAPI-->>Client: 授权无法完成
    else 元数据通过
        FAPI->>User: 未登录则先登录,再展示 consent
        alt consented 为 false
            User->>FAPI: POST consented=false
            FAPI-->>Client: access_denied
        else consented 为 true
            User->>FAPI: POST consented=true
            FAPI-->>Client: 重定向并返回 authorization code
            Client->>FAPI: POST /oauth/token,附带 code_verifier
            FAPI-->>Client: access token
            Client->>Resource: 携带 access token 访问 MCP 或资源
        end
    end

用户看见的页面可以是 Account Portal,也可以在应用自己的域名上。两种页面的 consent 都 POST 到 Frontend API。

  1. 发现。客户端读取 Clerk Frontend API 的 /.well-known/oauth-authorization-server。开发实例形如 https://verb-noun-00.clerk.accounts.dev/.well-known/oauth-authorization-server,生产实例形如 https://clerk.<domain>/.well-known/oauth-authorization-server。Publish CIMD support 打开时,元数据包含 client_id_metadata_document_supported: true。关闭时,元数据里没有这个字段。仍使用 legacy OAuth 的应用不发布该字段。同一份文档给出 authorization_endpoint(/oauth/authorize)和 token_endpoint(/oauth/token)。示例里 code_challenge_methods_supported 为 S256。
  2. 授权请求。客户端 GET authorization_endpoint。client_id 是元数据文档的 HTTPS URL,并带上 PKCE 的 code_challenge 与 code_challenge_method=S256。Clerk 的授权示例同时包含 response_type=code、redirect_uri 和 state。线上查询串要做百分号编码,这里按可读形式写出:
http
GET /oauth/authorize?response_type=code&client_id=https://client.example.com/oauth/client-metadata.json&redirect_uri=https://client.example.com/oauth/callback&code_challenge=<S256 challenge>&code_challenge_method=S256&state=<state>

客户端可以省略 scope。省略时,Clerk 套用 Default scopes for dynamic clients,并且 CIMD 客户端总会带上 offline_access。元数据文档不能自行扩大 scopes。

  1. 拉取并校验 CIMD。Clerk 向这个 client_id URL 取 JSON。校验要同时满足:文档内的 client_id 与被拉取的 URL 完全一致;请求里的 redirect_uri 与 redirect_uris 中的某一项精确匹配;客户端是公共客户端,token_endpoint_auth_method 为 none。含 client secret,或选择基于 secret 的 token 端点认证,会被拒绝。文档拉不到或校验失败时,授权完不成。
  2. 同意。CIMD 始终展示 consent,不能按应用关掉。默认页面是 Account Portal。正常的 OAuth 流程里,未登录用户会先进入登录,再进入同意页。

自建页面在 Dashboard 的 Paths → Component paths 里设置 OAuth consent 位置。开发实例填开发主机上的路径,例如 /oauth-consent;生产实例填与实例同一可注册域上的 https:// URL。读取元数据用 useOAuthConsent(),提交地址用 buildConsentActionUrl()。官方示例里的调用是:

tsx
const { data, isLoading, error } = useOAuthConsent({
  oauthClientId: clientId,
  scope,
  redirectUri,
})

const actionUrl = clerk.oauthApplication.buildConsentActionUrl({ clientId })

oauthClientId 来自授权请求的 client_id;空字符串或省略时 hook 不发请求。scope 与 redirectUri 可选。传入 redirectUri 后,返回值包含按 Public Suffix List 解析的 redirectDomain。表单使用 method="POST",action 为 actionUrl。允许和拒绝是名为 consented 的提交字段,值分别为 true 和 false。除 consented 和 organization_id 外,原授权参数用隐藏字段原样转发,避免查询串覆盖这两个表单字段。不要手写 Frontend API 的提交 URL。

承载同意流的页面必须把 referrer policy 设为 strict-origin-when-cross-origin,跨源 POST 才会带上 Origin,Clerk 才能校验 CSRF。用户拒绝时,客户端收到 OAuth 错误 access_denied。

Clerk 建议默认使用 Account Portal。只改外观时用预置组件 <OAuthConsent />;useOAuthConsent 是需要完全自绘时的低层接口。步骤与安全清单见 Set up a custom OAuth consent page。

  1. 换 token。用户允许后,Clerk 把浏览器重定向到已匹配的 redirect_uri,并带上 authorization code。客户端向发现文档中的 token_endpoint(/oauth/token)做授权码交换,提交 code_verifier,不提交 client_secret。CIMD 元数据要求 token_endpoint_auth_method 为 none,示例元数据的 grant_types_supported 包含 authorization_code。
  2. 访问资源。客户端用 access token 调用受保护资源。MCP 场景里,用户在同意页选择 Allow 之后,宿主用这个 token 访问已配置的 MCP server URL。向 Clerk 读取用户资料时,官方示例请求 /oauth/userinfo,并把 token 放在 Authorization: Bearer。

与 Context7 式同意页的对照

用户看到的形态相近:client_id 是 HTTPS URL,授权请求带 PKCE S256,页面上是 Allow / Deny。

差别在授权服务器落在谁的域名上。Context7 一类产品可以自己运行 authorization server,同意页和 /oauth/authorize 都在该产品自己的源站。IdP 若是 Clerk,发现、授权和换 token 都在该实例的 Frontend API 域名上,例如 https://<instance>.clerk.accounts.dev/oauth/authorize,或生产环境的 https://clerk.<domain>/oauth/authorize。资源应用自己的 origin 不是授权端点。

自建同意页改的是用户看见页面的域名。Dashboard 可以把 OAuth consent 指到应用域名上的路由。该页用 useOAuthConsent 读元数据,表单仍 POST 到 clerk.oauthApplication.buildConsentActionUrl({ clientId }),提交回到 Clerk Frontend API。

与传统 client_id、DCR 的差异

维度预注册传统客户端Dynamic Client RegistrationCIMD
client_id 形态不透明字符串注册响应下发HTTPS 元数据 URL
密钥机密客户端可有 secret常随注册下发无 secret;公共客户端
身份来源你在 AS 上创建AS 写接口创建记录客户端托管文档 + AS 拉取校验
运维面低:名单可控高:开放注册端点中:可预审 URL,也可允许未知后补审
典型场景自有 Web/Native App需要自助注册且对方不支持 CIMDMCP、第三方公共客户端

一句话:传统 ID 是「我发给你的编号」;CIMD 是「你公布的可验证名片」;DCR 是「你到我柜台现场办证」。

适用场景

适合

  • 对外提供 MCP(或同类 OAuth 资源),希望 Cursor / Claude 等宿主用稳定 URL 身份接入
  • 公共客户端无法保管 secret,又希望避免默认打开 DCR
  • 需要在 Dashboard 里按 Client ID URL 预审 scopes,或事后审查「隐式准入」过的客户端

不适合或需谨慎

  • 仍使用 Clerk legacy OAuth 实现的应用(不支持 CIMD)
  • 机密服务器端应用(本就该用传统预注册 + secret)
  • 未准备好 consent 流或 redirect 精确匹配策略时贸然「允许任意兼容客户端」

对 MCP / 公共客户端的意义

MCP 授权模型依赖 OAuth:宿主以客户端身份向你的授权服务器要 token,再访问你的 MCP 资源。CIMD 让宿主:

  1. 不必向你申请一枚私有 client_id
  2. 不必走 DCR 注册写路径
  3. 仍能通过 HTTPS 元数据证明「我是谁、回调去哪」

Clerk 在 MCP 接入指南中建议:能 CIMD 就用 CIMD;仅当客户端不支持时再开 Dynamic Client Registration。对提供 MCP 的产品而言,这意味着更短的接入路径,以及用 Client admission 管住「谁可以连」而不是把写注册端点敞着。

在 Clerk 里如何开启(概要)

资料日期:GA changelog 2026-09-21;默认关闭,不影响既有 OAuth,直到你开启 Publish CIMD support。

  1. Dashboard → OAuth applications → Settings → Client onboarding
  2. 打开 Publish CIMD support(向元数据发布 CIMD 支持;同时影响未知客户端是否可连)
  3. 按需设置 Client admission:
    • Any compatible CIMD client:未知也可连,首次连接记为 Implicitly allowed
    • Pre-registered and previously connected clients:新客户端须预注册;此前已接受的仍可
    • Pre-registered clients only:仅预注册;也会挡住曾隐式准入、现未预注册的客户端
  4. Applications 标签:Add application → Pre-register CIMD client,填 Client ID URL 与允许的 scopes,再 Allow client(Clerk 会拉取元数据;失败则显示 Error,需 Refresh metadata)
  5. 可配置 Default scopes for dynamic clients(客户端省略 scope 时的默认值;至少一项,并自动含 offline_access)

安全建议(与官方 MCP 提示一致):若只想接受已审核客户端,在同一次变更里打开 Publish CIMD 并收紧 Client admission,避免先广告 CIMD、后补限制的窗口期让未知客户端被记入隐式准入。预注册目前以 Dashboard 为准。

MCP 接入指南中的 agent 提示是:客户端支持 CIMD 时优先 CIMD;只有对方不支持 CIMD 时才打开 DCR。查看当前实例设置:

sh
npx clerk@latest api instance/oauth_application_settings

同一次 PATCH 可以同时发布 CIMD,并要求新客户端预注册。指南对这段载荷的说明是:已连接过的客户端除非已经被挡住,否则仍然允许。

sh
npx clerk@latest api instance/oauth_application_settings -X PATCH -d '{"client_id_metadata_documents_advertised": true, "client_id_metadata_documents_only_allow_pre_registered_clients": true}'

client_id_metadata_documents_advertised 对应 Publish CIMD support。client_id_metadata_documents_only_allow_pre_registered_clients 在该说明里对应 Dashboard 的 Pre-registered and previously connected clients:新客户端要预注册,此前已连接且未被挡住的客户端仍可连。要同时挡住曾经隐式准入、目前未预注册的客户端,在 Dashboard 选择 Pre-registered clients only。预注册客户端本身仍只在 Dashboard 操作。

只有客户端不支持 CIMD 时,同一指南才给出打开 DCR 并要求 PKCE 的示例:

sh
npx clerk@latest api instance/oauth_application_settings -X PATCH -d '{"dynamic_oauth_client_registration": true, "pkce_required": true}'

注意事项与边界

  • Redirect 必须精确匹配;尾斜杠、localhost 变体、自定义 scheme 都要写进元数据。
  • 元数据必须长期可拉:拉取失败则流程无法完成。
  • Block 隐式客户端不会删记录,也不撤销已发 token。
  • 删除客户端后,若仍允许未知客户端,对方可再连并再次变成 Implicitly allowed。
  • Consent 不可达(关掉 Account Portal 又未配自建 consent)时,不要放行 CIMD。
  • Scopes 权威在服务端配置;客户端文档不能「自授」更大权限。
  • 与自定义 OAuth scopes、同意页定制、Device Authorization 等能力正交:CIMD 解决的是客户端身份形态,不是全部 OAuth 产品面。

决策备忘

问题倾向
是否对外提供 MCP / 公共 OAuth?是 → 评估开启 CIMD
客户端是否支持 URL 型 client_id?是 → CIMD;否 → 再考虑 DCR 或传统预注册
能否接受任意未知客户端首次连入?否 → Publish CIMD + Pre-registered only,并预登记 URL
是否仍在 legacy OAuth?是 → 需迁到当前 OAuth 实现后再用 CIMD

落地时注意

  • Dashboard 预注册的 Public Client 使用不透明 client_id,可以按应用关闭 consentScreenEnabled。官方默认展示同意页,并建议保持开启。动态注册的应用不能关闭这个开关。CIMD 授权流始终展示 consent。MCP 或第三方宿主走 CIMD 时,用户会在同意页上看到 Allow / Deny;第一方 Public Client 关掉同意页,不影响这条 CIMD 路径。
  • 是否以及何时 Publish CIMD,是产品决定。打开之后,未知 URL 客户端能不能连入由 Client admission 控制;只接受已审核客户端时,发布与收紧准入放在同一次变更里。
  • 支持 URL 型 client_id 的 MCP 宿主优先 CIMD。对方没有 CIMD 时才考虑 DCR;不需要时保持 DCR 关闭。

参考

上述 Clerk 页面于 2026-10-21 复核。

本文共 3885 字,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。

评论

博客助手

正在打开博客助手…