Authorization Code Flow + PKCE
ทำไมต้องใช้ Authorization Code + PKCE?
หัวข้อที่มีชื่อว่า “ทำไมต้องใช้ Authorization Code + 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 การเรียก
ขั้นตอนของ flow
หัวข้อที่มีชื่อว่า “ขั้นตอนของ flow”- แอปสร้าง
code_verifierแบบสุ่ม แล้วคำนวณcode_challenge(SHA-256 hash เข้ารหัสแบบ base64url) - แอป redirect browser ไปยัง authorization endpoint ของ Keycloak พร้อม:
response_type=code,client_id,redirect_uri,scope=openid,code_challenge,code_challenge_method=S256, และstate - Keycloak แสดงหน้า login ผู้ใช้กรอก credential และยืนยันตัวตน
- Keycloak redirect กลับมายัง
redirect_uriของแอปพร้อม authorizationcode(และค่าstateสำหรับป้องกัน CSRF) - แอปส่ง
codeและcode_verifierไปยัง token endpoint ของ Keycloak (POST /token) เพื่อแลกเป็น token - Keycloak ตรวจสอบว่า
SHA-256(code_verifier)ตรงกับcode_challengeที่ส่งมาก่อน ถ้าตรงจะส่งคืนaccess_token,id_token, และrefresh_token
การแลก token (ขั้นตอนที่ 5)
หัวข้อที่มีชื่อว่า “การแลก token (ขั้นตอนที่ 5)”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}"ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Authorization Code + PKCE | Token ไม่เคยผ่าน 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 หรือแอปมือถือ
- ข้าม
stateparameter —stateใช้ป้องกัน 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