ข้ามไปยังเนื้อหา

Security and Authorization

ระบบ MCP ของจริงไม่เคยเป็นแค่ “client กับ server” request หนึ่งครั้งต้องข้าม trust boundary หลายชั้น และแต่ละชั้นคือจุดที่ credential อาจรั่ว ถูกเอาไปใช้ซ้ำ หรือถูกปลอมได้:

flowchart LR
  U["User"] -->|"อนุมัติ action"| H["Host (AI app)"]
  H -->|"client หนึ่งตัวต่อหนึ่ง server"| C["MCP Client"]
  C -->|"MCP ผ่าน stdio หรือ HTTP"| S["MCP Server"]
  S -->|"credential ของตัวเอง"| T["Third-party API"]
  A["Authorization Server"] -.->|"ออก token ให้ S"| C
trust boundary ที่ tool call ครั้งเดียวต้องข้าม
Boundaryสิ่งที่ข้ามไปคำถามที่ต้องถาม
User ↔ Hostการยินยอมให้รัน tool, ดู form ของ elicitation, เปิด URLuser รู้ไหมว่า server ไหนกำลังขอ และขออะไร
Host ↔ Clienttool definition และผลลัพธ์ที่จะเข้าไปในโมเดลdescription และผลลัพธ์ของ tool ถูกมองเป็น input ที่เชื่อไม่ได้หรือเปล่า
Client ↔ Serverrequest พร้อม bearer token บน HTTPtoken นี้ออกมา สำหรับ server ตัวนี้ จริงไหม
Server ↔ Third-party APIcredential ของ server เอง สำหรับ upstream serviceserver แอบเอา token ของ client ไปใช้แทนของตัวเองหรือเปล่า

โมดูลนี้ว่าด้วยสาม boundary หลัง ส่วน boundary แรกเป็นเรื่อง UX ที่พูดถึงไปแล้วใน Server Primitives และ Client Interaction

authorization spec ผูกกับ transport โดยรวมมันเป็น OPTIONAL และเมื่อ server รองรับ:

  • Streamable HTTP server SHOULD implement OAuth 2.1 flow ตามที่อธิบายในโมดูลนี้
  • stdio server SHOULD NOT implement มัน เพราะ stdio server คือ subprocess ที่ host รันขึ้นมาบนเครื่องของ user เอง มันรับ credential จาก environment (env ใน config ของ host, keychain, config file) เหมือน CLI ทั่วไป
  • custom transport MUST ทำตามแนวปฏิบัติด้าน security ที่เป็นมาตรฐานของ protocol นั้น ๆ

เหตุผลตรงไปตรงมา: stdio server ไม่มี network endpoint ให้ต้องป้องกัน และ host ก็ตัดสินใจไว้แล้วว่าเชื่อใจมันพอที่จะรันมัน ส่วน remote HTTP server ใครก็ยิง POST ถึงได้ มันเลยต้องมีวิธีรู้ว่า ใคร กำลังถาม และ ได้รับสิทธิ์อะไร มาบ้าง

// Host config for a stdio server: credentials come from the environment
{
"mcpServers": {
"github": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@example/github-mcp"],
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
}
}
}

MCP ไม่ได้คิดค้น authentication protocol ใหม่ แค่เอาบทบาทของ OAuth 2.1 มาแปะให้ผู้เล่นที่มีอยู่แล้ว:

ฝ่ายใน MCPบทบาท OAuth 2.1หน้าที่
MCP server (HTTP)Resource serverรับ bearer token, ตรวจสอบ (signature, วันหมดอายุ, audience, scope) และตอบ 401/403 เมื่อไม่ผ่าน
MCP clientOAuth clientค้นหา authorization server, ลงทะเบียนหรือระบุตัวตน, รัน authorization-code flow พร้อม PKCE แทน user แล้วส่ง token ไปกับทุก request
Authorization serverAuthorization serverauthenticate user, แสดงหน้า consent, ออก access token (และ refresh token ถ้ามี) สำหรับ resource ที่ระบุ
UserResource ownerอนุญาตหรือปฏิเสธไม่ให้ client เข้าถึง server

authorization server จะอยู่ข้าง ๆ MCP server หรือเป็น identity provider แยกต่างหากก็ได้ spec ตั้งใจไม่พูดถึงวิธี implement มันเลย แค่บังคับให้ MCP server ชี้ไปหามัน ผ่าน Protected Resource Metadata ซึ่งจะพูดถึงในบทถัดไป

OAuth ส่วนใหญ่เป็น “SHOULD” แต่มีสามข้อที่เป็น “MUST” และเป็นข้อที่กัน token ไม่ให้เดินข้าม boundary ไปมั่ว ๆ:

  1. Audience binding client ขอ token สำหรับ server ตัวนี้ (พารามิเตอร์ resource=) และ server MUST ตรวจว่า token ถูกออกให้ตัวเองจริง token ของ https://mail.example.com ใช้กับ https://mcp.example.com ไม่ได้ แม้ authorization server ตัวเดียวกันจะเซ็นทั้งคู่ก็ตาม
  2. ห้าม token passthrough server MUST NOT รับ token ที่ไม่ได้ออกให้ตัวเอง และ MUST NOT ส่งต่อ token ของ client ไปยัง downstream API ใด ๆ การเรียก upstream ต้องใช้ credential ของ server เอง
  3. Bearer header เท่านั้น token เดินทางใน Authorization: Bearer … กับทุก request ห้ามใส่ใน query string ของ URL เพราะมันจะไปโผล่ใน log และ browser history

ทุกอย่างที่เหลือในโมดูลนี้ต่อยอดจากสามข้อนี้

authorization spec ของ MCP ประกอบขึ้นจาก ส่วนย่อย ของงาน IETF และ OpenID ที่มีอยู่แล้ว ไม่ได้นิยาม crypto ใหม่ คุณไม่ต้องอ่านทั้งหมด แต่ควรจำชื่อได้:

มาตรฐานMCP ใช้ทำอะไร
OAuth 2.1 (draft-ietf-oauth-v2-1)flow พื้นฐาน: authorization code + PKCE ไม่มี implicit grant
RFC 6750การใช้ bearer token และรูปแบบ challenge ใน WWW-Authenticate
RFC 9728Protected Resource Metadata: วิธีที่ server ประกาศว่า authorization server ของตัวเองอยู่ที่ไหน
RFC 8414การค้นหา Authorization Server Metadata (/.well-known/oauth-authorization-server)
OpenID Connect Discovery 1.0discovery document อีกแบบ (/.well-known/openid-configuration)
RFC 8707Resource indicator: พารามิเตอร์ resource= ที่ผูก token เข้ากับ server หนึ่งตัว
RFC 9207พารามิเตอร์ iss ตอน redirect ที่ใช้กัน mix-up attack
Client ID Metadata Documents (draft)ใช้ HTTPS URL เป็น client identifier แทน dynamic registration
RFC 7591Dynamic Client Registration ซึ่งตอนนี้ deprecated แล้ว แต่ยังเก็บไว้เพื่อ compatibility
บทเรียนคุณจะได้เรียนรู้
The Authorization Flowลำดับเต็ม ๆ ตั้งแต่ 401 จนได้ bearer token ที่ใช้งานได้ พร้อม header และ document ตัวจริง
Client Registrationpre-registration, Client ID Metadata Documents และทำไม dynamic registration ถึงถูก deprecate
Protecting Your Serverตรวจ token ใน SDK ของ TypeScript และ Python, scope และ step-up challenge
Threats and Mitigationstoken passthrough, confused deputy, SSRF, handle hijacking, prompt injection และวิธีหยุดแต่ละอย่าง
Third-Party Authorizationเมื่อ server ของคุณต้องมีสิทธิ์เข้าถึง API อื่น ด้วยตัวเอง และ URL elicitation ช่วยให้มันปลอดภัยได้อย่างไร
authorization spec ของ MCP เขียนขึ้นสำหรับ transport แบบไหน?
ในแง่ OAuth 2.1 HTTP MCP server เล่นบทบาทอะไร?
server ได้รับ token ที่ถูกต้องและยังไม่หมดอายุ แต่ถูกออกให้ service อื่นโดย authorization server ตัวเดียวกัน server ต้องทำอย่างไร?