Episode ini mengamankan server MCP dengan OAuth 2.1: pemisahan peran resource server dan authorization server, header Mcp-Authorization, refresh token, scope accumulation untuk step-up auth, bound token DPoP, serta mitigasi SSRF dan CRLF pada URL server.

Episode 9 men-deploy server MCP ke HTTP dan membuka scaling horizontal. Tapi endpoint publik tanpa autentikasi adalah pintu terbuka — siapa pun bisa memanggil tool kalian, termasuk tool berbahaya. Episode ini menutup celah itu dengan Authorization OAuth 2.1 plus praktik security yang wajib dipahami sebelum server menyentuh produksi.
Roadmap: pahami dua peran utama (resource server dan authorization server), alur authorization code dengan PKCE, penggunaan header Mcp-Authorization dan refresh token, step-up authorization dengan scope accumulation, bound token DPoP, lalu mitigasi SSRF dan CRLF pada URL server.
OAuth 2.1 membagi tanggung jawab menjadi dua entitas:
Alur yang lazim: aplikasi host mengarahkan user ke authorization server untuk login, mendapatkan authorization code, lalu menukarnya dengan access token. Token inilah yang nanti dikirim ke server MCP pada setiap request. Server MCP tidak perlu mengerti password user — cukup memvalidasi token yang diterima.
Warning
Jangan simpan rahasia klien di kode frontend. Authorization code flow dengan PKCE menjaga code_verifier tetap di sisi aplikasi host; rahasia klien untuk aplikasi publik tidak boleh dibocorkan ke browser.
Alur yang direkomendasikan OAuth 2.1 adalah Authorization Code + PKCE. Sebelum mengarahkan user, aplikasi membuat code_verifier (string acak) dan code_challenge (hash-nya). Setelah user login dan menyetujui scope, authorization server mengembalikan code; aplikasi menukarnya dengan token sambil mengirim code_verifier sebagai bukti kepemilikan.
curl -X POST https://auth.example.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=abc123" \
--data-urlencode "redirect_uri=https://host.example.com/callback" \
--data-urlencode "code_verifier=verifier-rahasia"PKCE mencegah serangan authorization code interception: tanpa code_verifier yang benar, code curian tidak bisa ditukar menjadi token. Serangan replay juga diredam karena setiap code hanya valid sekali dan berumur sangat pendek.
Setelah punya access token, setiap request ke server MCP membawa token di header Mcp-Authorization:
curl https://mcp.example.com/mcp \
-H "Content-Type: application/json" \
-H "Mcp-Authorization: Bearer access-token-123" \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Mcp-Authorization adalah header khusus MCP yang dibaca resource server untuk mengekstrak token — pemisahannya dari Authorization biasa mencegah konflik saat MCP di-embed di aplikasi yang sudah memakai OAuth untuk API-nya sendiri.
Access token biasanya berumur pendek (misal 15 menit). Saat kedaluwarsa, aplikasi tidak perlu memaksa user login ulang — cukup menukar refresh token yang valid untuk mendapatkan access token baru. Refresh token disimpan aman oleh aplikasi host dan diputar ulang (rotated) setiap kali dipakai; jangan pernah ekspos ke resource server.
Ada kalanya request membutuhkan izin yang lebih tinggi daripada yang sudah disetujui — misalnya transfer-funds baru boleh dijalankan setelah user mengonfirmasi ulang. OAuth 2.1 menangani ini lewat scope accumulation: aplikasi mengajukan authorization tambahan dengan scope yang lebih spesifik, user menyetujui, dan token baru menggabungkan scope lama dengan scope baru.
Step-up ini berpasangan sempurna dengan MRTR dari episode 8: saat server MCP membalas pending karena butuh izin tambahan, host menjalankan alur OAuth step-up, lalu melanjutkan request dengan messageId yang sama dan token baru. Pengalaman user-nya: satu dialog "izinkan transfer dana" muncul tepat saat dibutuhkan — bukan di awal sesi.
Token biasa (bearer token) bersifat "siapa yang pegang, dia yang pakai" — kalau token bocor di log atau proxy, penyerang bisa memakainya. Bound token (misalnya lewat DPoP, RFC 9449) mengikat token ke kunci publik milik klien: setiap request harus menyertakan bukti kepemilikan kunci privat yang cocok, sehingga token yang dicuri tanpa kunci privat tidak berguna.
const headers = {
Authorization: `DPoP ${accessToken}`,
DPoP: dpopProof,
};dpopProof adalah JWS yang menandatangani kombinasi method HTTP, URL, dan jti acak. DPoP menaikkan standar keamanan secara signifikan untuk token yang melewati banyak hop. Untuk server MCP produksi yang menangani data sensitif, ini layak dijadikan syarat.
Server MCP yang menerima URL dari user — misalnya tool "fetch halaman" — rawan SSRF (Server-Side Request Forgery): penyerang menyuruh server mengakses localhost atau metadata service internal. Validasi ketat sebelum koneksi:
function sanitizeUrl(raw: string): string {
const url = new URL(raw);
if (url.protocol !== "https:") {
throw new Error("Hanya URL https yang diizinkan");
}
if (/[\r\n]/.test(raw) || /%0d|%0a/i.test(raw)) {
throw new Error("CRLF terdeteksi pada URL");
}
return url.toString();
}new URL(raw) mem-parsing input menjadi komponen terpisah sehingga validasi lebih mudah dilakukan. Perlindungan yang perlu dipasang:
https, tolak http dan skema lain.localhost, range private (misal 10.x, 172.16-31.x, 192.168.x), dan link-local.\r\n (atau encoding %0d/%0a) untuk injeksi header HTTP.Episode 10 menutup sisi keamanan akses: model OAuth 2.1 dengan pemisahan resource server dan authorization server, alur PKCE, header Mcp-Authorization, refresh token, scope accumulation untuk step-up auth, bound token/DPoP, serta mitigasi SSRF dan CRLF.
Inti yang harus dibawa pulang:
code_verifier harus selalu disaring dari output logging.Di episode 11 berikutnya kita eksplorasi extension resmi MCP Apps & UI Resources — tool yang tidak hanya mengembalikan teks, tapi juga interface interaktif yang dirender host dalam sandboxed iframe. Sampai jumpa!