真实生产项目用 Claude 开发,公开完整 prompt 和代码。是学习 AI 工程化开发的最佳参考。
@cloudflare/workers-oauth-provider 为 HTTP API 和在 Cloudflare Workers 上运行的远程 MCP 服务器添加 OAuth 2.1 授权。
它提供了稳定 MCP 2026-07-28 授权规范所需的授权服务器和受保护资源部分。它面向基于 HTTP 的 MCP 传输,不实现 MCP 传输或协议方法。Stdio 服务器应该从其环境中获取凭证。
npm install @cloudflare/workers-oauth-provider
Worker 需要一个绑定为 OAUTH_KV 的 KV namespace:
{
"kv_namespaces": [
{
"binding": "OAUTH_KV",
"id": "YOUR_KV_NAMESPACE_ID",
},
],
}
要启用客户端 ID 元数据文档,还需要添加 Cloudflare 的 SSRF 保护兼容性标志:
{
"compatibility_flags": ["global_fetch_strictly_public"],
}
参见客户端注册获取匹配的提供程序选项。
提供程序接受普通 ExportedHandler 对象或扩展 WorkerEntrypoint 的类。此示例两者都使用。
import { OAuthProvider, type AuthRequest, type OAuthHelpers } from '@cloudflare/workers-oauth-provider';
import { WorkerEntrypoint } from 'cloudflare:workers';
interface AuthProps {
userId: string;
displayName: string;
}
interface Env {
OAUTH_KV: KVNamespace;
OAUTH_PROVIDER: OAuthHelpers;
}
class McpApiHandler extends WorkerEntrypoint<Env, AuthProps> {
fetch(request: Request): Response {
return Response.json({
authenticated: true,
userId: this.ctx.props.userId,
displayName: this.ctx.props.displayName,
});
}
}
const defaultHandler: ExportedHandler<Env> = {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname !== '/authorize') {
return new Response('Not found', { status: 404 });
}
// This parses the OAuth parameters and validates the client, redirect URI,
// response type, resource indicators, and configured PKCE restrictions.
let oauthRequest: AuthRequest;
try {
oauthRequest = await env.OAUTH_PROVIDER.parseAuthRequest(request);
} catch {
// Do not redirect until the client and redirect URI have been validated.
return new Response('Invalid authorization request', { status: 400 });
}
const client = await env.OAUTH_PROVIDER.lookupClient(oauthRequest.clientId);
if (!client) {
return new Response('Unknown OAuth client', { status: 400 });
}
// Authenticate the user and obtain consent here. Do not automatically
// approve a request in production. This example assumes those steps have
// produced the following user and scope values.
const user = { id: 'user-123', displayName: 'Ada' };
const grantedScopes = oauthRequest.scope.filter((scope) => scope === 'mcp:read');
const { redirectTo } = await env.OAUTH_PROVIDER.completeAuthorization({
request: oauthRequest,
userId: user.id,
metadata: { clientName: client.clientName },
scope: grantedScopes,
props: {
userId: user.id,
displayName: user.displayName,
},
});
return Response.redirect(redirectTo, 302);
},
};
export default new OAuthProvider<Env>({
apiRoute: '/mcp',
apiHandler: McpApiHandler,
defaultHandler,
authorizeEndpoint: '/authorize',
tokenEndpoint: '/oauth/token',
scopesSupported: ['mcp:read'],
allowPlainPKCE: false,
resourceMetadata: {
resource: 'https://mcp.example.com/mcp',
authorization_servers: ['https://mcp.example.com'],
scopes_supported: ['mcp:read'],
resource_name: 'Example MCP server',
},
// Preferred for clients with no pre-existing relationship.
// Also requires global_fetch_strictly_public in wrangler.jsonc.
clientIdMetadataDocumentEnabled: true,
// Optional compatibility fallback. MCP 2026 deprecates DCR for new clients.
clientRegistrationEndpoint: '/oauth/register',
});
apiRoute 和 apiHandler 用单个处理程序保护一个或多个路由前缀。当不同的前缀需要不同的处理程序时,使用 apiHandlers。
在调用受保护的处理程序之前,提供程序读取 bearer token,拒绝缺失、无效或过期的凭证,检查其受众,并通过 ctx.props 暴露已认证的应用程序数据。处理程序不需要解析或验证 token,但它仍然必须强制执行应用程序权限,如 scope、所有权和租户。
受保护路由前缀外的请求转到 defaultHandler。在上面的示例中,该处理程序拥有 /authorize。
MCP 客户端按照 MCP 授权服务器发现规则分两个阶段发现授权。
对于 https://mcp.example.com/mcp 处的 MCP 端点:
客户端向 /mcp 发送未认证的请求。
提供程序返回 401 Unauthorized,包含类似以下的挑战:
WWW-Authenticate: Bearer realm="OAuth", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"
客户端获取受保护资源元数据:
https://mcp.example.com/.well-known/oauth-protected-resource/mcp
该文档通过 authorization_servers 标识一个或多个授权服务器发行者。
客户端获取该提供程序的 RFC 8414 授权服务器元数据:
https://mcp.example.com/.well-known/oauth-authorization-server
元数据告诉客户端在哪里授权、交换 token 以及是否启用注册。
受保护资源元数据和授权服务器元数据作用不同:
提供程序始终在以下地址提供 RFC 9728 元数据:
/.well-known/oauth-protected-resource
它还支持路径特定的元数据。请求:
/.well-known/oauth-protected-resource/public/mcp
生成 https://example.com/public/mcp 作为派生资源,除非 resourceMetadata.resource 覆盖它。
对于 MCP 部署,显式配置规范 MCP 端点:
resourceMetadata: {
resource: 'https://mcp.example.com/mcp',
authorization_servers: ['https://auth.example.com'],
scopes_supported: ['files:read'],
bearer_methods_supported: ['header'],
resource_name: 'Files MCP server',
}
authorization_servers 可能包含多个发行者。MCP 客户端选择一个授权服务器,必须为每个发行者分别保管凭证和 token。
提供程序发布包含以下内容的 RFC 8414 元数据:
该包提供 RFC 8414 元数据而非 OpenID Connect discovery。MCP 授权服务器需要提供至少其中一种机制,所以 RFC 8414 就足够了。
你的 authorizeEndpoint 属于应用程序的 defaultHandler,因为用户认证和同意是特定于应用程序的。提供程序不是身份提供程序。
典型的流程有三个步骤:
completeAuthorization() 在写入授权或撤销现有授权之前重复响应类型验证。应用程序仍然负责呈现本地授权错误,以及仅在客户端和重定向 URI 验证后构造任何终端 OAuth 错误重定向。
completeAuthorization() 存储新授权,并默认在新授权安全存储后撤销相同用户和客户端的现有授权。仅当应用程序有意允许相同用户和客户端的并发授权时,才设置 revokeExistingGrants: false。
对于拥有许多授权的用户,revokeExistingGrantsBatchSize 控制扫描期间使用的 KV 页面大小。它默认为 50,上限为 KV 最大页面大小 1000。
RFC 9207 发行者标识始终启用。授权服务器元数据播发 authorization_response_iss_parameter_supported: true,成功的授权响应自动包含 iss。
parseAuthRequest() 返回预期的发行者。如果应用程序创建终端 OAuth 错误重定向,包含该值:
const oauthRequest = await env.OAUTH_PROVIDER.parseAuthRequest(request);
const redirect = new URL(oauthRequest.redirectUri);
redirect.searchParams.set('error', 'access_denied');
redirect.searchParams.set('state', oauthRequest.state);
if (oauthRequest.issuer) redirect.searchParams.set('iss', oauthRequest.issuer);
return Response.redirect(redirect.toString(), 302);
中间身份提供程序重定向和本地 HTML 错误页面不需要 OAuth iss 参数。
MCP 客户端注册定义了三种方式供客户端获取客户端 ID。支持所有三种方式的客户端首选预注册,然后是 CIMD,最后是 DCR。
使用 OAuthHelpers.createClient() 通过应用程序或管理代码创建客户端。这些客户端存储在 KV 中,不受 clientRegistrationTTL 限制。
CIMD 允许客户端使用带有非根路径的 HTTPS URL 作为其 client_id。该 URL 提供一个 JSON 元数据文档,描述客户端及其重定向 URI。
在两个地方启用它:
new OAuthProvider({
// Other options...
clientIdMetadataDocumentEnabled: true,
});
{
"compatibility_flags": ["global_fetch_strictly_public"],
}
兼容性标志防止出站 CIMD 获取使用旧版同区域源路由,这对 SSRF 保护是必要的。提供程序仅当两个设置都存在时才播发 client_id_metadata_document_supported: true。
CIMD 验证包括:
CIMD 目前仅支持 token_endpoint_auth_method: "none"。
当无法获取或验证 CIMD 文档时,token 端点返回通用 invalid_client 响应,并通过 onError.internal 报告诊断。OAuthHelpers 方法抛出导出的 CimdFetchError,允许应用程序区分上游元数据失败与不存在的客户端。参见高级配置获取示例。
设置 clientRegistrationEndpoint 启用 RFC 7591 动态客户端注册:
clientRegistrationEndpoint: '/oauth/register';
MCP 2026-07-28 为新实现弃用 DCR,转而支持 CIMD。端点仍对不支持 CIMD 的客户端的兼容性有用。
注册仅接受由配置的提供程序实现的认证方法、授权和响应类型,在存储前拒绝不一致的授权/响应组合。省略的元数据使用 RFC 7591 默认值:client_secret_basic、grant_types: ["authorization_code"] 和 response_types: ["code"]。
clientRegistrationTTL 控制动态注册客户端的生命周期。默认为 90 天。
disallowPublicClientRegistration 拒绝使用 token_endpoint_auth_method: "none" 的 DCR 客户端。
clientRegistrationCallback 可以基于应用程序策略允许或拒绝注册。
由 OAuthHelpers.createClient() 创建的客户端不受 DCR TTL 或公共注册限制影响。
公共客户端必须在授权码流中使用 PKCE。提供程序始终支持 S256。
对于新 MCP 部署,禁用旧版普通方法:
allowPlainPKCE: false;
allowPlainPKCE 默认为 true 以实现向后兼容性。allowImplicitFlow 默认为 false;对于 MCP 和其他新 OAuth 部署保持禁用。
提供程序拥有 tokenEndpoint。它交换授权码获取 token,刷新访问 token,并处理 RFC 7009 撤销。刷新 token 在使用时轮换。立即之前的 token 保持有效,直到其替换首次被使用,允许客户端在丢失刷新响应后重试。
MCP 客户端必须在授权和 token 请求中发送规范 MCP 服务器 URI 作为资源。提供程序解析 RFC 8707 资源指示符,在授权中存储授权资源,将其用作访问 token 的受众,并拒绝资源扩展或受众不匹配。
资源策略遵循 resourceMetadata.resource:
路径感知的受众使用路径边界前缀匹配。https://example.com/mcp 的 token 可用于 /mcp/tools,但不能用于 /mcp-other。分离部署和需要路径隔离的部署应该显式配置规范资源。
resourceMatchOriginOnly 仍然是对路径感知资源引入前创建的授权的迁移选项。不要为新部署启用它。
scopesSupported 仅在授权服务器元数据中发布。显式配置 resourceMetadata.scopes_supported,用最小的 scope,这些 scope 是基本受保护资源功能和基线 Bearer 挑战所需的。
应用程序通过 completeAuthorization({ scope }) 决定授予哪些请求的 scope。Token 和刷新请求只能缩小这些 scope。
提供程序不向 API 处理程序暴露标准有效 token 授权上下文,也不强制操作级 scope 策略。受保护资源元数据在 Bearer 挑战中提供基线 scope 指导。高级集成可以通过外部 token 验证提供操作特定的 step-up 指导。
该包还支持:
参见高级配置获取示例和安全说明。
敏感值不以明文存储:
授权 userId 和 metadata 不加密,因为应用程序使用它们枚举和撤销授权。将这些字段视为存储可见的元数据。
参见 storage-schema.md 获取完整的 KV 布局。
KV TTL 自动移除过期记录。purgeExpiredData() 为孤立或过期的授权和 token 提供手动扫描:
const provider = new OAuthProvider({
// Options...
});
export default {
fetch(request, env, ctx) {
return provider.fetch(request, env, ctx);
},
async scheduled(_event, env) {
const result = await provider.purgeExpiredData(env, { batchSize: 100 });
console.log(result);
},
};
默认批量大小为 50。result.done 报告两个密钥空间是否在该调用期间完全扫描。
通过 OAuthHelpers.deleteClient() 删除客户端也会跨用户撤销其授权和关联的 token。
查阅导出的 OAuthProviderOptions、回调接口和 src/oauth-provider.ts 中的 JSDoc 获取完整的类型化 API。
处理程序接收 env.OAUTH_PROVIDER,它实现 OAuthHelpers。它可以:
getOAuthApi(options, env) 在 fetch 处理程序外提供相同的辅助程序 API,包括 RPC 方法和其他 Worker 入点。
该包实现或支持以下相关部分:
需要 Node 24 或更新版本。
npm install
npm run build
npm run check
npm run prettier
影响行为或公共 API 的更改需要 Changeset。参见 AGENTS.md 获取仓库约定和 SECURITY.md 获取漏洞报告。
Kenton Varda 关于如何创建此库的原始帐户保存在 HISTORY.md 中。