1. OAuth 2.0, OIDC และ PKCE คืออะไร? ทำไมโลกเว็บและโมบายสมัยใหม่ขาดไม่ได้?
ใน Day 1 เราติดตั้ง Keycloak เป็นเซิร์ฟเวอร์กลางสำเร็จแล้ว แต่คำถามถัดมาคือ: "เมื่อผู้ใช้กดปุ่ม Login บนหน้าเว็บหรือแอปมือถือ ข้อมูลถูกส่งไปหา Keycloak อย่างไร และ Keycloak ตอบอะไรกลับมาให้แอปใช้งาน?"
ในอดีต (ยุคก่อนปี 2010) หากแอปพลิเคชันภายนอกต้องการเข้าถึงข้อมูลของคุณ (เช่น แอปพิมพ์รูปต้องการดึงรูปภาพจาก Google Drive) ทางเดียวคือคุณต้อง "กรอก Username และ Password ของ Google ให้แอปพิมพ์รูปไปตรงๆ"! ซึ่งอันตรายอย่างยิ่ง เพราะแอปนั้นจะได้รหัสผ่านตัวจริงของคุณไปทำอะไรก็ได้
"มอบอำนาจให้ทำอะไรได้บ้าง (Delegated Access)"
- เป็นกรอบการทำงาน (Framework) สำหรับแจก Access Token เพื่อใช้เรียก API
- ไม่ได้บอกว่าผู้ใช้คือใคร ชื่ออะไร หรืออีเมลอะไร (ไม่สน Identity)
- เปรียบเสมือน "บัตรผ่านประตูที่ระบุแค่ว่า เข้าห้องไหนได้บ้าง"
"ยืนยันว่าคุณคือใคร (Identity Assertion)"
- เป็นชั้นมาตรฐานระบุตัวตนที่สร้างครอบอยู่บน OAuth 2.0 อีกชั้นหนึ่ง
- เพิ่ม ID Token (JWT) ที่มีข้อมูลของผู้ใช้ (ชื่อ, อีเมล, sub/ID) ให้หน้าแอปแสดงผล
- จัดเตรียม Endpoint
/userinfoให้ดึงข้อมูลโปรไฟล์เพิ่มเติม
🛡️ แล้ว PKCE คืออะไร? ทำไมแอปเว็บ SPA และแอปมือถือถึงต้องใช้?
แอปพลิเคชันแบ่งเป็น 2 ประเภทหลักตามความสามารถในการรักษาความลับ:
ทำงานอยู่บนเซิร์ฟเวอร์ปิดหลังบ้าน สามารถเก็บ Client Secret (รหัสผ่านประจำแอป) ไว้เป็นความลับได้ 100% บุคคลภายนอกไม่สามารถมองเห็นได้
โค้ดทั้งหมดทำงานอยู่บนเบราว์เซอร์หรือเครื่องของลูกค้า ห้ามใส่ Client Secret เด็ดขาด เพราะผู้ใช้สามารถกด Inspect ดู Source Code หรือ Decompile ไฟล์ APK/IPA เพื่อขโมย Secret ได้ทันที!
เมื่อ Public Client ไม่มี Client Secret ทาง IETF จึงคิดค้นมาตรฐาน PKCE (Proof Key for Code Exchange - RFC 7636) ขึ้นมา โดยใช้หลักการ "สร้างรหัสลับเฉพาะกิจแบบสุ่มขึ้นมาใช้ชั่วคราว (One-time Dynamic Secret) ในทุกๆ ครั้งที่มีการขอล็อกอิน" ทำให้แม้จะมีคนแอบดักจับ Authorization Code ไปได้ ก็ไม่สามารถนำไปแลกเป็น Token ได้หากไม่มีรหัสลับที่แท้จริง
📚 ศัพท์เทคนิคสำคัญประจำโปรโตคอล OIDC & PKCE (Formal Technical Glossary)
ตารางนิยามศัพท์ที่เป็นรากฐานของข้อสอบและงานวิศวกรรม IAM ทั้งหมด:
The Airport Luggage Claim with Cryptographic Seal
เปรียบเทียบกลไก PKCE เสมือน "การส่งกระเป๋าสัมภาระที่สนามบิน":
ผู้เดินทางสุ่มรหัสลับขึ้นมาในใจ (code_verifier) และส่งเพียงตราประทับลายนิ้วมือที่แฮชแล้ว (code_challenge) ให้เจ้าหน้าที่เคาน์เตอร์
เมื่อเจ้าหน้าที่ส่งตั๋วชั่วคราว (authorization_code) วิ่งผ่านสายพานเปิด
แม้จะมีมิจฉาชีพแอบขโมยตั๋วใบนั้นไปได้ แต่มิจฉาชีพก็ไม่สามารถนำตั๋วไปเปิดรับกระเป๋าตัวจริง (Tokens) ได้
เพราะไม่มีรหัสลับต้นฉบับที่อยู่ในใจของผู้เดินทางตัวจริง!
The $1.2M Custom URI Scheme Hijack: Insecure Public Client & Authorization Code Theft
เงินฝากและสินทรัพย์ดิจิทัลถูกถ่ายโอนออกจากบัญชีผู้ใช้ 480 ราย ก่อนที่ธนาคารจะระงับการทำงานของระบบ
ใช้ Public Client โดยไม่ออกแบบ Proof Key (PKCE) และยอมรับ Wildcard Redirect URI ส่งผลให้มัลแวร์ดักจับ Authorization Code ได้สำเร็จ
บังคับใช้ PKCE โหมด S256 เสมอ, แบน Implicit Flow อย่างเด็ดขาด และห้ามใช้ Wildcard ใน Redirect URIs
สถานการณ์จริง: แอปพลิเคชัน Neo-Bank บนสมาร์ตโฟนแห่งหนึ่ง ได้ทำการต่อ Authentication เข้ากับ Keycloak โดยใช้ Authorization Code Flow สำหรับการส่ง Authorization Code กลับมายังแอป ทีมนักพัฒนาได้จดทะเบียน Custom URI Scheme ชื่อ bankapp://oauth-callback และตั้งค่า Redirect URI บนเซิร์ฟเวอร์แบบผ่อนปรนเป็น bankapp://*
เนื่องจากเป็น Mobile Application (Public Client) ทีมงานจึงไม่ได้ใส่ Client Secret และที่ร้ายแรงที่สุดคือ ไม่ได้เปิดใช้งาน PKCE (Proof Key for Code Exchange)
ผู้โจมตีได้สร้างมัลแวร์แฝงมาในคราบแอปอัปเดตสภาพอากาศ แล้วจดทะเบียน Intent Filter บนระบบปฏิบัติการของเหยื่อเพื่อดักจับ Custom Scheme bankapp://oauth-callback เมื่อเหยื่อเข้าสู่หน้าจอ Login ผ่าน Browser และกดยืนยันตัวตนสำเร็จ IdP จึง Redirect Code กลับมาที่เครื่อง ผลปรากฏว่ามัลแวร์สามารถสกัดกั้น (Intercept) นำ code=AUTH_CODE_XYZ ไปได้ทันที และเนื่องจากไม่มีกลไก PKCE ตรวจสอบ มัลแวร์จึงยิง HTTP POST ตรงเข้าหา /token endpoint เพื่อแลกเป็น Access Token และ Refresh Token ของเหยื่อ สวมรอยโอนเงินออกไปได้สำเร็จนับล้านดอลลาร์
S256 เสมอ เพื่อสร้าง Dynamic Cryptographic Secret สำหรับยืนยันตัวตนในทุกๆ Login Session โดยห้ามใช้ Implicit Flow และห้ามใช้ Wildcard Redirect URI เป็นอันขาด!"
Anatomy of OIDC Authorization & Token Endpoints
ทุกการทำ OIDC Handshake ประกอบด้วยสองฝั่ง: Front-Channel (ผ่าน Browser Redirect) และ Back-Channel (Direct HTTPS POST Request) ซึ่งต้องส่งค่าพารามิเตอร์ให้ถูกต้องตามมาตรฐาน RFC:
ดึงข้อมูล Metadata ของ Realm เพื่อสำรวจ Public Endpoints และ Supported Algorithms จาก Keycloak ที่เรารันไว้ใน Day 1
# Query OpenID Connect Discovery Metadata สำหรับ Realm 'app-dev'
curl -s http://localhost:8080/realms/app-dev/.well-known/openid-configuration | jq '{
issuer: .issuer,
authorization_endpoint: .authorization_endpoint,
token_endpoint: .token_endpoint,
jwks_uri: .jwks_uri,
code_challenge_methods_supported: .code_challenge_methods_supported
}'
สร้าง Cryptographic Pairs สดๆ บน Terminal โดยไม่ต้องพึ่งไลบรารีภายนอก และนำไปแลก Token
# 1. สร้าง Code Verifier แบบสุ่ม (Base64URL 43-128 ตัวอักษร)
CODE_VERIFIER=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')
echo "Code Verifier: $CODE_VERIFIER"
# 2. คำนวณ Code Challenge ด้วย SHA256 + Base64URL
CODE_CHALLENGE=$(printf "%s" "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
echo "Code Challenge: $CODE_CHALLENGE"
# 3. นำ URL ด้านล่างไปเปิดบน Web Browser เพื่อ Login รับ Authorization Code
echo "เปิด URL นี้ในเบราว์เซอร์: http://localhost:8080/realms/app-dev/protocol/openid-connect/auth?client_id=frontend-spa&response_type=code&scope=openid%20profile%20email&redirect_uri=http://localhost:3000/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=random_state_123"
# 4. เมื่อ Browser redirect ไปที่ http://localhost:3000/callback?code=AUTH_CODE_HERE ให้นำ code มายิงแลก Token (ต้องใช้ HTTP POST):
curl -X POST http://localhost:8080/realms/app-dev/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=frontend-spa" \
-d "redirect_uri=http://localhost:3000/callback" \
-d "code=ใส่_AUTH_CODE_ที่ได้จาก_URL_ตรงนี้" \
-d "code_verifier=$CODE_VERIFIER"
- เปิดใช้งาน
Implicit Flowบน Single Page App (SPA) - ใส่ Wildcard ใน Valid Redirect URIs เช่น
https://myapp.com/*หรือ* - ไม่เปิด
PKCEหรือเลือกใช้ challenge method เป็นplain - พยายามฝัง Client Secret ไว้ในโค้ด JavaScript หรือ Mobile App
- ไม่ตรวจสอบพารามิเตอร์
stateในหน้า Callback
- เปิดเฉพาะ
Standard Flow (Authorization Code)และปิด Implicit Flow อย่างเด็ดขาด - ระบุ Exact Redirect URI เช่น
https://app.corp.com/callback(ห้ามใช้ Wildcard) - บังคับใช้ PKCE Code Challenge Method =
S256ใน Advanced Client Settings - ตั้งค่า SPA/Mobile เป็น Public Client (ไม่มี Secret) และใช้ Refresh Token Rotation
- ตรวจสอบความถูกต้องของ
stateและnonceก่อนบันทึก Session
Live PKCE Generator & Protocol Verification Sandbox
ทดลองสร้าง code_verifier และดูการคำนวณ code_challenge แบบ Real-time ผ่าน Web Cryptography API พร้อมก้าวผ่านแต่ละสเต็ปของโปรโตคอล OIDC
code_challenge แต่ ยังไม่ส่ง code_verifier ออกจากเครื่องเด็ดขาด
ผู้โจมตีดักจับได้ code=AUTH_CODE_xyz จาก URL แต่เมื่อส่ง request ไปที่ /token โดยไม่มี code_verifier ตัวจริงของเหยื่อ Keycloak จะตอบกลับเป็น HTTP 400 Bad Request: "PKCE verification failed: invalid code_verifier" การโจมตีล้มเหลว 100%!
Day 2 OIDC Core & PKCE Cryptographic Mastery Exam
ครอบคลุม OIDC vs OAuth 2.0, PKCE S256 Mechanics, Nonce/State validation, Public Clients และทบทวนสาระสำคัญจาก Day 1
Day 2 Practical Deliverable (ภารกิจประจำวัน)
ก่อนก้าวไปสู่ Day 3 (Client Scopes, Token Custom Claims & Protocol Mappers) คุณต้องทำภารกิจต่อไปนี้ให้สำเร็จบนสภาพแวดล้อมจริง: