Appearance
身份验证
验证你的用户
许多Apps SDK应用可以匿名运行,但用户特定或写入操作需要身份验证。你可以与现有的授权服务器集成,或使用每个工具的安全配置来混合匿名和经过身份验证的端点。
使用OAuth 2.1的自定义身份验证
组件
- 资源服务器 – 暴露工具并验证访问Token的MCP服务器
- 授权服务器 – 颁发Token的身份提供者
- 客户端 – 代表用户行事的ChatGPT
必需端点
你的授权服务器必须提供:
/.well-known/oauth-protected-resource
/.well-known/openid-configuration
token_endpoint
registration_endpoint
实践中的流程
- ChatGPT查询MCP服务器以获取资源元数据
- ChatGPT向授权服务器注册
- 用户进行身份验证并同意作用域
- ChatGPT将授权代码交换为访问Token
- 服务器在每个请求上验证Token
代码示例(Python)
python
from mcp.server.fastmcp import FastMCP
from mcp.server.auth.settings import AuthSettings
from mcp.server.auth.provider import TokenVerifier, AccessToken
class MyVerifier(TokenVerifier):
async def verify_token(self, token: str) -> AccessToken | None:
payload = validate_jwt(token, jwks_url)
if "user" not in payload.get("permissions", []):
return None
return AccessToken(
token=token,
client_id=payload["azp"],
subject=payload["sub"],
scopes=payload.get("permissions", []),
claims=payload,
)
mcp = FastMCP(
name="kanban-mcp",
stateless_http=True,
token_verifier=MyVerifier(),
auth=AuthSettings(
issuer_url="https://your-tenant.us.auth0.com",
resource_server_url="https://example.com/mcp",
required_scopes=["user"],
),
)
validate_jwt
是一个辅助函数,它使用来自授权服务器JWKS端点的公钥验证Token签名和过期时间。如果Token有效,验证器会返回一个 AccessToken
对象,MCP服务器会将其附加到工具上下文中,以便你的工具实现可以访问用户的身份和作用域。
代码示例(TypeScript)
typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { OAuth2Settings, TokenVerifier } from "@modelcontextprotocol/sdk/server/auth.js";
const verifier: TokenVerifier = async (token: string) => {
const payload = await validateJwt(token, jwksUrl);
if (!payload.permissions.includes("user")) {
return null;
}
return {
token,
clientId: payload.azp,
subject: payload.sub,
scopes: payload.permissions,
claims: payload,
};
};
const server = new McpServer({
name: "kanban-server",
version: "1.0.0",
auth: {
oauth2: {
issuerUrl: "https://your-tenant.us.auth0.com",
resourceServerUrl: "https://example.com/mcp",
requiredScopes: ["user"],
},
},
tokenVerifier: verifier,
});
使用 securitySchemes
的每个工具身份验证
可以使用两种方案类型定义每个工具的身份验证:
"noauth"
– 可以匿名调用"oauth2"
– 需要OAuth 2.0
TypeScript SDK示例
公共搜索工具:
typescript
server.registerTool(
"search",
{
title: "搜索文档",
securitySchemes: [
{ type: "noauth" },
{ type: "oauth2", scopes: ["search.read"] },
],
inputSchema: { query: z.string() },
},
async ({ query }, context) => {
// 如果用户已通过身份验证,上下文包含accessToken
const results = await searchDocs(query, context.accessToken);
return {
content: [{ type: "text", text: `找到 ${results.length} 个结果` }],
structuredContent: { results },
};
}
);
创建文档工具(需要身份验证):
typescript
server.registerTool(
"create_doc",
{
title: "创建文档",
securitySchemes: [{ type: "oauth2", scopes: ["docs.write"] }],
inputSchema: { title: z.string(), content: z.string() },
},
async ({ title, content }, context) => {
if (!context.accessToken) {
throw new Error("需要身份验证");
}
const doc = await createDoc(title, content, context.accessToken);
return {
content: [{ type: "text", text: `已创建文档: ${doc.id}` }],
structuredContent: { doc },
};
}
);
Python SDK示例
python
@mcp.tool(
title="搜索文档",
security_schemes=[
{"type": "noauth"},
{"type": "oauth2", "scopes": ["search.read"]},
],
)
async def search(query: str, context: ToolContext) -> ToolResponse:
results = await search_docs(query, context.access_token)
return ToolResponse(
content=[{"type": "text", "text": f"找到 {len(results)} 个结果"}],
structured_content={"results": results},
)
@mcp.tool(
title="创建文档",
security_schemes=[{"type": "oauth2", "scopes": ["docs.write"]}],
)
async def create_doc(title: str, content: str, context: ToolContext) -> ToolResponse:
if not context.access_token:
raise ValueError("需要身份验证")
doc = await create_doc_api(title, content, context.access_token)
return ToolResponse(
content=[{"type": "text", "text": f"已创建文档: {doc['id']}"}],
structured_content={"doc": doc},
)
测试和推出建议
- 从开发租户开始
- 在广泛推出之前使用受信任的测试者
- 计划Token轮换和撤销
开发租户
大多数身份提供者允许你创建单独的开发和生产租户。在开发租户中测试你的OAuth流程,以避免影响生产用户。
受信任的测试者
在公开推出之前,邀请一小组用户测试你的身份验证流程。这有助于你在更广泛的受众看到之前捕获边缘情况和可用性问题。
Token轮换和撤销
实现Token刷新,以便用户在Token过期时不必重新进行身份验证。还提供一种让用户撤销访问的方法,这对于安全和合规性很重要。
最佳实践
最小化作用域请求
仅请求你的应用实际需要的作用域。这减少了用户同意摩擦并提高了安全性。
使用短期Token
将访问Token寿命保持在短期(例如15分钟),并使用刷新Token进行长期访问。这限制了Token泄露的影响。
记录访问
记录所有身份验证的操作,以便你可以审计访问并调试问题。包括用户ID、操作和时间戳。
处理Token过期
优雅地处理Token过期,方法是捕获401响应并提示用户重新进行身份验证或自动刷新Token。
支持Token撤销
实现Token撤销端点,以便用户可以撤销访问,你的服务器可以使Token无效。
故障排除
无效Token错误
如果你看到"无效Token"错误:
- 验证Token签名是否与授权服务器的JWKS端点匹配
- 检查Token是否已过期
- 确保Token包含所需的作用域
授权循环
如果用户陷入授权循环:
- 检查你的
redirect_uri
是否与注册的URI完全匹配 - 验证你的授权服务器是否正确实现了PKCE
- 确保你的服务器正确处理授权代码交换
CORS错误
如果你在开发中看到CORS错误:
- 确保你的授权服务器允许来自你的开发域的跨源请求
- 添加
Access-Control-Allow-Origin
头部到你的响应 - 对于生产环境,使用你的生产域配置CORS
后续步骤
通过身份验证,你可以构建需要用户身份和权限的工具。浏览存储页面,了解如何持久化用户数据,或查看示例页面以获取完整的端到端演示。