Tôi đã có một bài dài về MCP OAuth resource server. Nhưng sau khi đọc lại spec mới nhất và vài nghiên cứu gần đây, tôi thấy cần viết một bài khác ngắn hơn, thẳng vào chỗ dễ hiểu sai nhất: MCP OAuth không phải chỉ là đem login OAuth quen thuộc gắn vào một API mới.
OAuth cho web app thường trả lời câu hỏi: user này có cho app này truy cập tài khoản không. OAuth cho MCP remote server phải trả lời thêm mấy câu khó hơn:
- Agent này đang gọi thay user nào?
- Token này được mint cho MCP server nào?
- Tool nào được gọi, với scope nào?
- Client chưa từng gặp Authorization Server này thì register ra sao?
- Nếu agent nối cùng lúc 10 MCP server, token của server A có bị chuyển nhầm sang server B không?
Đó là lý do MCP authorization spec nhìn hơi “quá tay”: OAuth 2.1, Protected Resource Metadata, Authorization Server Metadata, Client ID Metadata Documents, Dynamic Client Registration, Resource Indicators, PKCE. Không phải spec thích phức tạp. Nó đang cố đóng các lỗ mà API key và OAuth kiểu cũ để hở khi một agent bắt đầu gọi tool thay người dùng.
Cái mới trong spec 2025-11-25
Bài OAuth cũ của tôi bám theo spec 2025-06-18. Bản 2025-11-25 vẫn giữ ý chính: protected MCP server là OAuth 2.1 Resource Server. MCP server validate token và serve resource/tool, không tự biến mình thành Authorization Server nếu không cần.
Nhưng có một thay đổi đáng chú ý: Client ID Metadata Documents được đẩy lên như hướng chính cho trường hợp client và server chưa có quan hệ trước. Dynamic Client Registration vẫn còn, nhưng chuyển thành fallback/backward compatibility.
Nói dễ hiểu:
- Cách cũ: client POST
/register, Authorization Server cấpclient_id. - Cách mới hơn:
client_idcó thể là một URL HTTPS trỏ tới metadata JSON của client.
Ví dụ:
{
"client_id": "https://app.example.com/oauth/client-metadata.json",
"client_name": "Example MCP Client",
"redirect_uris": [
"http://127.0.0.1:3000/callback"
],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
Authorization Server thấy client_id là URL, fetch metadata document, kiểm tra client_id trong document khớp đúng URL, rồi validate redirect URI từ document đó. Với hệ MCP, chuyện này hợp lý hơn DCR trong nhiều trường hợp: Claude Desktop, IDE, agent client, hoặc app nội bộ có thể publish identity metadata của chính nó mà không cần mỗi Authorization Server cấp trước một client ID riêng.
Nhưng đổi lại, Authorization Server giờ phải nghĩ tới SSRF, cache metadata, redirect URI validation, và trust boundary của document đó. Một URL làm client_id nghe gọn, nhưng nó biến “đăng ký OAuth client” thành một lần fetch từ server lạ. Đây là mặt mới của attack surface.
Protected Resource Metadata: server nói “đừng login với tôi”
Trong web app OAuth quen thuộc, developer thường biết sẵn Authorization Server nằm ở đâu. Với MCP, client có thể gặp một remote server lạ lần đầu. Nó chưa biết server này dùng Auth0, Keycloak, Okta, Logto, custom IdP hay một gateway riêng.
Vì vậy MCP server phải expose OAuth 2.0 Protected Resource Metadata. Khi client gọi không có token, server trả 401 kèm WWW-Authenticate trỏ tới metadata:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
Metadata đó nói đại ý:
{
"resource": "https://mcp.example.com/mcp",
"authorization_servers": ["https://auth.example.com"],
"scopes_supported": ["mcp:tools", "mcp:resources"]
}
Đây là câu quan trọng: MCP server không nói “đưa username/password cho tôi”. Nó nói “tôi là protected resource, token hợp lệ phải đến từ Authorization Server này.”
Sai lầm phổ biến là build MCP server rồi tự nhét API key vào config client. Cách đó chạy nhanh trong demo, nhưng không giải quyết được user identity, consent, revoke, scope, hoặc audit. Với remote server, đó không còn là shortcut vô hại.
Resource Indicator: token phải biết nó dành cho server nào
OAuth web app thường tập trung vào user và client. MCP còn cần một thứ nữa: resource. Khi client xin token, nó phải chỉ rõ token này dành cho MCP server nào.
Ví dụ authorization request phải có parameter:
resource=https%3A%2F%2Fmcp.example.com%2Fmcp
Authorization Server dùng resource indicator để mint token có audience đúng. MCP server nhận token thì validate audience. Nếu token dành cho https://crm.example.com/mcp bị đem qua https://github.example.com/mcp, server GitHub phải reject.
Đây là cách spec chống token passthrough và confused deputy ở tầng cơ bản. Agent không được giữ một token rộng rồi chuyển lung tung giữa nhiều MCP server. Mỗi token phải có audience rõ.
Nếu bạn chỉ nhớ một điều trong bài này, nhớ điều này: OAuth cho MCP mà không validate audience theo resource thì vẫn hở đúng lỗi mà spec đang cố sửa.
Confused deputy không còn là lý thuyết
Confused deputy trong MCP có hình dạng rất thực tế.
Một agent được user tin tưởng có thể nối nhiều MCP server cùng lúc: Gmail, GitHub, Notion, internal CRM. Nếu server độc hại hoặc bị compromise khiến agent gửi nhầm credential/token qua tool call, server đó có thể hành động bằng quyền không dành cho nó.
Spec OAuth 2.1 giúp giảm một phần rủi ro bằng audience binding, PKCE, scope, discovery rõ ràng. Nhưng nó không tự giải quyết hết:
- Token đúng audience vẫn có thể quá rộng scope.
- Server có thể expose tool nguy hiểm dưới tên nghe vô hại.
- Consent screen có thể không đủ chi tiết tới cấp tool.
- Gateway có thể xác thực user nhưng không enforce authorization theo tool.
- Agent có thể bị prompt injection để gọi tool hợp lệ theo cách sai.
Nói cách khác: OAuth trả lời “ai được vào server này”. Nó chưa tự trả lời đủ “tool này có được gọi trong tình huống này không”.
Đó là lý do các bài gần đây bắt đầu nói tới consent theo tool, policy gateway, audit log, và delegated authorization cho agent. Đây là lớp phía trên OAuth, không phải thay thế OAuth.
Nghiên cứu thực tế: server remote đang lỗi nhiều
Một paper tháng 05/2026 đo auth security của MCP remote servers tìm được 7.973 live remote MCP servers. Trong đó 40,55% expose tools không có authentication. Với nhóm OAuth-enabled test được, nghiên cứu nói mỗi server đều có ít nhất một flaw, và dynamic client registration flaws ảnh hưởng 96,6% server được test.
Tôi không dùng một paper để kết luận cả ecosystem hỏng. Nhưng nó xác nhận cảm giác khi đọc issue và blog post về MCP auth: đây là vùng mới, nhiều implementation copy nhanh từ sample, và OAuth cho MCP có nhiều edge case hơn OAuth login web app.
Nếu bạn đang ship MCP server public trong năm 2026, câu hỏi không phải “có auth chưa”. Câu hỏi là:
- Protected Resource Metadata có đúng không?
- Authorization Server discovery có đúng không?
- Client registration dùng Client ID Metadata, preregistration, hay DCR?
- DCR có allow redirect URI bừa không?
- Token có validate
iss,aud,exp, signature, scope không? - Scope có map tới tool cụ thể không?
- Audit log có ghi user, client, tool, resource, decision không?
Nếu câu trả lời là “chắc có”, thì chưa đủ.
Scope theo server chưa đủ
Giả sử MCP server có 20 tools:
search_docsread_invoicecreate_invoicedelete_invoicesend_emailexport_customer_data
Nếu token chỉ có scope mcp:tools, bạn mới biết user được gọi “tools” nói chung. Nhưng production cần chi tiết hơn:
mcp:docs:read
mcp:invoice:read
mcp:invoice:write
mcp:email:send
mcp:customer:export
Tốt hơn nữa, server nên enforce policy theo cả user, client, tool, arguments, và environment. Ví dụ user có quyền invoice:write nhưng tool call create_invoice với amount quá lớn vẫn cần approval. OAuth scope là đầu vào cho authorization, không phải toàn bộ authorization.
Đây là chỗ nhiều MCP gateway bắt đầu có đất sống: nó đứng giữa agent và server, thêm consent, policy, audit, credential vault, và tool-level decision. Nhưng dù dùng gateway, resource indicator và audience validation vẫn phải đúng. Gateway không phải lý do để bỏ chuẩn.
Local stdio khác remote HTTP
Một nhầm lẫn khác: đem toàn bộ OAuth flow áp cho local stdio server.
MCP docs nói rõ authorization flow này dành cho HTTP-based transports. Với stdio local server, credential thường lấy từ environment hoặc thư viện auth local. Lý do đơn giản: stdio server là subprocess chạy trên máy bạn. Nó không nhận request từ internet, không cần redirect browser để chứng minh remote client là ai.
Nhưng đừng vì thế mà xem local server là an toàn tuyệt đối. Local server vẫn có rủi ro credential leakage, env var leak, prompt injection, tool poisoning. Chỉ là cơ chế auth khác: .env, keychain, ~/.config, OS credential store, hoặc CLI login token.
Rule thực dụng:
- Local stdio: tập trung vào secret storage, env scrub, permission, allowed tools.
- Remote HTTP: dùng OAuth 2.1 resource server profile, metadata discovery, audience validation, scope, audit.
Trộn hai mô hình này thường sinh ra hệ rất khó debug.
Checklist trước khi ship remote MCP server
Nếu tôi phải review một MCP server production, đây là checklist tối thiểu:
- Server trả
401vớiWWW-Authenticateđúng, córesource_metadata. /.well-known/oauth-protected-resourcetồn tại,resourcecanonical,authorization_serversđúng.- Authorization Server metadata support OAuth 2.1, PKCE, token endpoint, discovery rõ ràng.
- Client registration strategy rõ: pre-registration, Client ID Metadata, hay DCR fallback.
- Nếu dùng Client ID Metadata, AS chống SSRF và validate redirect URI từ metadata.
- Nếu dùng DCR, không accept redirect URI bừa, không cấp confidential secret cho public client vô nghĩa.
- Token validation kiểm
iss,aud,exp, signature/JWKS hoặc introspection. audphải khớp resource URI của MCP server.- Scope map tới tool/resource cụ thể, không chỉ
mcp:*. - Tool có side effect cần approval/policy riêng, không chỉ dựa vào OAuth.
- Audit log ghi user, client, tool, arguments summary, decision, timestamp.
- Không bao giờ nhận token của upstream SaaS rồi pass-through nguyên vẹn sang MCP server khác.
Nếu thiếu một trong các dòng này, server vẫn có thể chạy, nhưng chưa nên gọi là production-ready.
Kết luận
MCP OAuth 2.1 đáng viết riêng vì nó là nơi agent infrastructure bắt đầu đụng identity thật. Login web app đã khó. Login cho một agent có thể gọi nhiều tool, nhiều server, nhiều hành động thay user còn khó hơn.
Điểm mới không phải “dùng OAuth”. Điểm mới là resource discovery, client metadata, audience binding, và authorization theo tool. Protected Resource Metadata giúp client biết token phải xin ở đâu. Client ID Metadata giúp client chưa từng được register vẫn có identity có thể kiểm tra. Resource Indicator giúp token không bị dùng nhầm server. Scope và policy giúp agent không biến một token hợp lệ thành hành động sai.
Nếu MCP là cách agent chạm vào hệ thống thật, thì OAuth 2.1 không phải phần phụ. Nó là hàng rào đầu tiên giữa “demo chạy được” và “hệ thống có thể để người khác dùng”.
Tham khảo
- MCP Authorization spec 2025-11-25
- MCP docs: Understanding Authorization in MCP
- RFC 9728: OAuth 2.0 Protected Resource Metadata
- RFC 8707: Resource Indicators for OAuth 2.0
- OAuth 2.1 draft
- A First Measurement Study on Authentication Security in Real-World Remote MCP Servers
- Authgear: MCP Authentication, OAuth 2.1 in MCP Explained