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

Authorization Code Flow + PKCE

นี่คือ flow ที่แนะนำสำหรับ public client — SPA และแอปมือถือ — ที่ไม่สามารถเก็บ client secret ได้อย่างปลอดภัย Implicit flow ล้าสมัยแล้วและไม่ควรใช้ในโปรเจกต์ใหม่

PKCE (Proof Key for Code Exchange) ป้องกันการโจมตีแบบดักจับ authorization code เนื่องจาก public client ไม่สามารถเก็บ secret ได้ PKCE จึงแทนที่ด้วยการที่แอปสร้าง secret ชั่วคราว (code_verifier) ในแต่ละ flow แล้วคำนวณ code_challenge จากค่านั้น และพิสูจน์ในภายหลังว่าถือ verifier ดั้งเดิมไว้เมื่อแลก code เป็น token

browser ของผู้ใช้ ทำ redirect ไปมา SPA หรือแอปมือถือ (client) เริ่ม flow และจัดการ redirect callback Keycloak (authorization server) ยืนยันตัวตนผู้ใช้และออก token API (resource server) รับ access token เพื่อ authorize การเรียก

  1. แอปสร้าง code_verifier แบบสุ่ม แล้วคำนวณ code_challenge (SHA-256 hash เข้ารหัสแบบ base64url)
  2. แอป redirect browser ไปยัง authorization endpoint ของ Keycloak พร้อม: response_type=code, client_id, redirect_uri, scope=openid, code_challenge, code_challenge_method=S256, และ state
  3. Keycloak แสดงหน้า login ผู้ใช้กรอก credential และยืนยันตัวตน
  4. Keycloak redirect กลับมายัง redirect_uri ของแอปพร้อม authorization code (และค่า state สำหรับป้องกัน CSRF)
  5. แอปส่ง code และ code_verifier ไปยัง token endpoint ของ Keycloak (POST /token) เพื่อแลกเป็น token
  6. Keycloak ตรวจสอบว่า SHA-256(code_verifier) ตรงกับ code_challenge ที่ส่งมาก่อน ถ้าตรงจะส่งคืน access_token, id_token, และ refresh_token

curl ด้านล่างแสดงการแลก token ในขั้นตอนที่ 5 ใส่ค่าของคุณก่อนรัน

curl -X POST https://${KC_URL}/realms/${REALM}/protocol/openid-connect/token \
  -d "grant_type=authorization_code" \
  -d "client_id=${CLIENT_ID}" \
  -d "code=${AUTH_CODE}" \
  -d "redirect_uri=${REDIRECT_URI}" \
  -d "code_verifier=${CODE_VERIFIER}"
ตัวเลือกBenefitCost
Authorization Code + PKCEToken ไม่เคยผ่าน browser URL ปลอดภัยสำหรับ public client และเป็นมาตรฐานปัจจุบันสำหรับ SPA และแอปมือถือต้องมี extra round-trip ไปยัง token endpoint และต้องจัดการ code_verifier/code_challenge เพิ่มเติมในโค้ด
Implicit flow (deprecated)Setup ง่ายกว่าในอดีต ไม่ต้องมี token endpoint call แยกToken หลุดไปอยู่ใน URL fragment เสี่ยงต่อการรั่วผ่าน browser history, referrer header, และ log — ถูกถอดออกจาก OAuth 2.1 แล้ว
Client Credentialsเหมาะกับ machine-to-machine ไม่ต้องมี user เข้ามาเกี่ยวข้องใช้ไม่ได้เลยถ้า flow ต้องมีผู้ใช้จริง เพราะไม่มี user context
  • ใช้ Implicit flow ในโปรเจกต์ใหม่ปี 2024 ขึ้นไป — Implicit flow ถูก deprecate ไปแล้วเพราะ access token ถูกส่งกลับมาผ่าน URL fragment ซึ่งอาจรั่วผ่าน browser history หรือ referrer header ทุกโปรเจกต์ใหม่ควรใช้ Authorization Code + PKCE แทนเสมอ ไม่ว่าจะเป็น SPA หรือแอปมือถือ
  • ข้าม state parameterstate ใช้ป้องกัน CSRF โดยให้แอป generate ค่าแบบสุ่มก่อน redirect แล้วตรวจสอบว่าค่าที่ได้กลับมาตรงกันตอน callback หากข้ามขั้นตอนนี้ผู้โจมตีสามารถหลอกให้ผู้ใช้ล็อกอินด้วย session ของผู้โจมตีเองได้
  • ใส่ client secret ลงใน SPA หรือแอปมือถือ — public client ไม่สามารถเก็บ secret ได้อย่างปลอดภัยเพราะโค้ด client-side ถูก inspect ได้เสมอ นี่คือเหตุผลที่ PKCE มีไว้แทนที่ secret ด้วย code_verifier ที่สร้างใหม่ทุกครั้ง
  • ไม่ validate code_challenge_method — ควรบังคับใช้ S256 เท่านั้น อย่ายอมให้ fallback ไปเป็น plain เพราะจะทำให้ PKCE ไม่ได้ช่วยป้องกันอะไรเลย

💡 ตัวอย่างจากของจริง

ปุ่ม “Log in with Google/GitHub” — เป็นตัวอย่าง Authorization Code + PKCE ที่ใช้งานจริงในระดับ production ทุกวันนี้ ทั้งเว็บและแอปมือถือใช้ pattern เดียวกันนี้เกือบทั้งหมด

แอป mobile banking — ใช้ Authorization Code + PKCE สำหรับหน้าจอ native login เช่นกัน เพราะแอปมือถือถือเป็น public client ไม่สามารถเก็บ client secret ได้อย่างปลอดภัย จึงต้องพึ่ง PKCE เพื่อป้องกันการดักจับ authorization code ระหว่าง redirect

เหตุใด PKCE จึงจำเป็นสำหรับ public client?
แอปส่งอะไรพร้อม authorization request เพื่อเปิดใช้ PKCE?
Keycloak ตรวจสอบอะไรเมื่อแลก code ที่ token endpoint?
Flow ใดที่เลิกใช้แล้วและไม่ควรใช้?