1. Client Credentials Grant คืออะไร? เมื่อไม่มีมนุษย์มาล็อกอิน ระบบยืนยันตัวตนกันอย่างไร?
ใน Day 2 และ Day 4 เราได้เรียนรู้การยืนยันตัวตนสำหรับผู้ใช้งาน (Human End-User) ผ่านทางหน้าจอเบราว์เซอร์ด้วย Authorization Code Flow + PKCE กันไปแล้ว แต่ในระบบซอฟต์แวร์ระดับองค์กร มีงานอีกประเภทหนึ่งที่ไม่เคยมีมนุษย์มานั่งหน้าจอเลย เช่น:
- Cron Job / Scheduled Worker: สคริปต์ที่ตื่นขึ้นมาตอนตี 2 เพื่อดึงรายการสั่งซื้อไปสรุปยอดบัญชี
- Event Consumer Daemon: เซอร์วิสที่คอยดึงข้อความจาก Message Queue แล้วยิงอัปเดตสต็อกสินค้า
- Microservice-to-Microservice (M2M): บริการชำระเงิน (Payment Service) เรียกตัดเงินที่บริการธนาคาร (Ledger Service)
คำถามคือ: เมื่อไม่มีมนุษย์มาพิมพ์ Username และ Password แล้วใครจะเป็นผู้ยืนยันตัวตน?
คำตอบคือ มาตรฐาน OAuth 2.0 ได้ออกแบบโฟลว์เฉพาะสำหรับเครื่องจักร เรียกว่า Client Credentials Grant (RFC 6749 Section 4.4)
ซึ่งแอปพลิเคชันจะใช้ "ตัวตนของตัวซอฟต์แวร์เอง" (Client Identity) โดยส่ง client_id และ client_secret ตรงไปยัง Keycloak เพื่อขอรับ Access Token สำหรับเรียกใช้งาน API เบื้องหลัง
"มีมนุษย์เป็นผู้ถือครองสิทธิ์และรหัสผ่าน"
- มีขั้นตอน Front-channel ผ่าน Browser 302 Redirect เพื่อให้คนกรอกรหัสผ่านและ 2FA
- มีพารามิเตอร์ PKCE (
code_challenge/code_verifier) เพื่อป้องกันการดักฟังบน Client - ได้รับ Refresh Token เพื่อรักษา Session เมื่อคนใช้งานต่อเนื่องโดยไม่ต้องพิมพ์รหัสผ่านใหม่
"เครื่องจักรคุยกันโดยตรง ไม่มีมนุษย์เกี่ยวข้อง"
- ไร้ Browser และไร้ Redirect: ส่ง HTTP POST ตรงไปยัง Token Endpoint (Back-channel 100%)
- ไม่ต้องใช้ PKCE: เพราะทำงานบนเซิร์ฟเวอร์หลังบ้านที่มีความปลอดภัย (Confidential Client)
- ไม่มี Refresh Token เด็ดขาด: เมื่อ Access Token หมดอายุ เครื่องจักรมี Client Secret อยู่แล้ว จึงยิงขอ Token ใบใหม่ได้ทันที
📚 ศัพท์เทคนิคสำคัญประจำโปรโตคอล M2M & Keycloak Service Accounts
ตารางนิยามคำศัพท์ที่เป็นรากฐานของข้อสอบและการเชื่อมต่อระหว่าง Microservices:
The Autonomous Night-Shift Security Drone vs. The Human Employee
เปรียบเทียบการยืนยันตัวตนในระบบ IAM เสมือนการเปิดประตูเข้าอาคารสำนักงานใหญ่:
• User Flow (Human Employee): เปรียบเหมือน "พนักงานมนุษย์" ที่ต้องเดินมาหน้าป้อมยาม แสดงหน้าตา สแกนใบหน้าและกรอกรหัส 2FA ยามจึงแจกบัตรผ่านประตูพร้อมให้กุญแจสำรอง (Refresh Token) เผื่อเดินเข้าออกในวันนั้น
• Machine Flow (Autonomous Drone / Background Worker): เปรียบเหมือน "โดรนรักษาความปลอดภัยอัตโนมัติ"
ที่บินอยู่ตอนตี 3 ไม่มีหน้าตามนุษย์ให้สแกน โดรนจะบินตรงไปยังแท่นชาร์จกลาง (Keycloak IdP) แล้วยื่นรหัสลับประจำชิปประมวลผล (Client Secret) ผ่านสัญญาณคลื่นเข้ารหัสส่วนตัว แท่นชาร์จจะปล่อยสติกเกอร์บาร์โค้ดผ่านประตูที่มีอายุ 5 นาที (Access Token) ให้โดรนบินไปตรวจห้องเก็บของ (Resource Server) หากสติกเกอร์หมดอายุ โดรนก็บินกลับมายื่นรหัสลับของชิปเพื่อขอสติกเกอร์แผ่นใหม่ได้เองทันทีโดยไม่ต้องปลุกใคร!
The $2.4M Orphan Service Account & Hardcoded Secret Breach
มูลค่าความเสียหายจากการถูกยึด Database และค่าปรับด้านความปลอดภัยจากการรั่วไหลของข้อมูลลูกค้า
ตั้ง Service Account ให้ถือสิทธิ์ realm-admin และฝัง Client Secret ไว้ใน Repository สาธารณะ
ห้ามให้สิทธิ์แอดมินแก่ Service Account, ใช้ Vault จัดเก็บ และวางระบบหมุนเวียนคีย์ลับอัตโนมัติ
สถานการณ์จริง: บริษัทผู้ให้บริการระบบโลจิสติกส์แห่งหนึ่ง ได้สร้าง Microservice ชื่อ tracking-sync
เพื่อทำหน้าที่ซิงก์พิกัดรถบรรทุกแบบอัตโนมัติ ในขั้นตอนการติดตั้งบน Keycloak เพื่อความรวดเร็วในการทดสอบ วิศวกรได้เข้าไปที่แท็บ Service Account Roles
แล้วมอบบทบาท realm-admin (สิทธิ์ผู้ดูแลระบบสูงสุดของ Realm) ให้กับ Service Account นี้ โดยตั้งใจว่าจะกลับมาลดสิทธิ์ในภายหลังแต่ลืมทำ
ต่อมา นักพัฒนาฝึกงานคนหนึ่งได้เผลอ Push โค้ดที่มีไฟล์คอนฟิก application.yaml ซึ่งมีค่า client_secret บันทึกอยู่ขึ้นไปยัง Public GitHub Repository
ภายในเวลาไม่ถึง 3 นาที บอทสแกนโค้ดของแฮกเกอร์ได้ตรวจพบคีย์ลับนี้ และยิงคำขอ HTTP POST ไปยัง Token Endpoint ของ Keycloak:
curl -X POST https://auth.logistics.th/realms/corp/protocol/openid-connect/token \
-d "grant_type=client_credentials" \
-d "client_id=tracking-sync" \
-d "client_secret=Leaked_Secret_Value_XYZ"
เนื่องจาก Service Account นี้ถือครองสิทธิ์ realm-admin แฮกเกอร์จึงได้รับ Token ที่มีพลังอำนาจเบ็ดเสร็จ แฮกเกอร์สามารถใช้ Token นี้เรียก Keycloak Admin REST API
เพื่อดาวน์โหลดข้อมูลพนักงานทุกคน, เปลี่ยน Password ของผู้บริหาร, และสั่งลบ Client ทั้งหมดในระบบทิ้งจนบริการอัมพาตทั้งประเทศ
realm-admin หรือสิทธิ์ระดับผู้ดูแลระบบให้กับ Service Account เป็นอันขาด! แต่ละ Worker ควรได้รับเฉพาะ Fine-Grained Client Roles เท่าที่มันจำเป็นต้องใช้ (เช่น tracking:write เท่านั้น) และ Secret ต้องถูกดึงจาก Secret Manager (เช่น HashiCorp Vault หรือ AWS Secrets Manager) พร้อมมีวงจร Zero-Downtime Secret Rotation สม่ำเสมอ!"
Anatomy of Service Account Provisioning via kcadm.sh CLI
การสร้าง M2M Client และการกำหนดสิทธิ์ให้ Service Account สามารถทำได้ผ่าน Keycloak Admin CLI (kcadm.sh) ตามขั้นตอนแบบทีละขั้น:
สร้าง Confidential Client ชื่อ billing-daemon โดยเปิดใช้งาน serviceAccountsEnabled=true
# 1. เข้าสู่ Container และล็อกอินผ่าน kcadm
docker exec -it keycloak-core /opt/keycloak/bin/kcadm.sh config credentials \
--server http://localhost:8080 --realm master --user superadmin --password AdminSuperSecret2026!
# 2. สร้าง Confidential Client พร้อมเปิด Service Accounts
docker exec -it keycloak-core /opt/keycloak/bin/kcadm.sh create clients -r app-dev \
-s clientId=billing-daemon \
-s name="Automated Billing Background Daemon" \
-s enabled=true \
-s publicClient=false \
-s serviceAccountsEnabled=true \
-s standardFlowEnabled=false \
-s directAccessGrantsEnabled=false
# 3. ตรวจสอบว่า Client สร้างสำเร็จและดึง Client Secret
CLIENT_UUID=$(docker exec -it keycloak-core /opt/keycloak/bin/kcadm.sh get clients -r app-dev -q clientId=billing-daemon --fields id --format csv | tail -n 1 | tr -d '\r')
docker exec -it keycloak-core /opt/keycloak/bin/kcadm.sh get clients/$CLIENT_UUID/client-secret -r app-dev
ดึง User ID ของ Service Account ที่ Keycloak สร้างให้โดยอัตโนมัติ แล้วผูกสิทธิ์ Role เฉพาะทาง พร้อมทดสอบยิงแลก Token ด้วย cURL
# 1. ค้นหา User Entity เสมือนของ Service Account
SA_USER_ID=$(docker exec -it keycloak-core /opt/keycloak/bin/kcadm.sh get clients/$CLIENT_UUID/service-account-user -r app-dev --fields id --format csv | tail -n 1 | tr -d '\r')
echo "Service Account User ID: $SA_USER_ID"
# 2. สร้าง Realm Role สำหรับงานเฉพาะทาง: 'invoice-writer'
docker exec -it keycloak-core /opt/keycloak/bin/kcadm.sh create roles -r app-dev -s name=invoice-writer -s description="Permission to generate and update invoices"
# 3. มอบสิทธิ์ Role 'invoice-writer' ให้กับ Service Account User
docker exec -it keycloak-core /opt/keycloak/bin/kcadm.sh add-roles -r app-dev --uid $SA_USER_ID --rolename invoice-writer
# 4. ทดสอบยิง HTTP POST แลก M2M Access Token ผ่าน cURL (Back-channel 100%)
CLIENT_SECRET=$(docker exec -it keycloak-core /opt/keycloak/bin/kcadm.sh get clients/$CLIENT_UUID/client-secret -r app-dev | jq -r '.value')
curl -s -X POST http://localhost:8080/realms/app-dev/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=billing-daemon" \
-d "client_secret=$CLIENT_SECRET" | jq .
- มอบสิทธิ์
realm-adminหรือ Service Account Admin ให้บอทเพื่อง่ายต่อการทดสอบ - ตั้งอายุ Access Token ยาวเป็นชั่วโมงหรือเป็นวันสำหรับ M2M Token
- ฝัง
client_secretไว้ใน Environment Variable ของ Container โดยตรงแบบ Plaintext - ไม่เคยเปลี่ยน Secret เลยตลอดอายุการใช้งาน (Static Credentials)
- เปิดใช้งาน
Standard FlowหรือDirect Access Grantsค้างไว้ใน M2M Client
- ปฏิบัติตาม Least Privilege: ให้เฉพาะ Client Roles สำหรับ Resource นั้นๆ
- กำหนด Short Lifespan (เช่น 60–300 วินาที) สำหรับ M2M Token
- หมุนเวียน Secret ผ่าน Secret Manager แบบ Zero-Downtime Overlapping Rotation
- สำหรับบริการที่ต้องการความปลอดภัยขั้นสูงสุด ให้ยกระดับไปใช้ Private Key JWT (RFC 7523)
- ปิดโฟลว์ที่ไม่เกี่ยวข้องทั้งหมด (เปิดเฉพาะ Service Accounts Enabled)
Live Client Credentials Exchanger & Zero-Downtime Secret Rotation
ทดลองจำลองการยิงคำขอของ Worker Service เพื่อขอ Access Token และสังเกตโครงสร้าง Payload ของ JWT ที่ได้ พร้อมทดลองหมุนเวียน Secret แบบ Zero-Downtime เพื่อดูว่าระบบจัดการคีย์สลับกันอย่างไร:
{
"iss": "http://localhost:8080/realms/app-dev",
"sub": "sa-uuid-99124-billing-daemon",
"client_id": "billing-daemon",
"resource_access": {
"billing-daemon": {
"roles": ["invoice-writer"]
}
},
"scope": "email profile",
"exp": 1791629100,
"iat": 1791628800
}
sub ของ Token จะไม่ใช่ ID ของมนุษย์ แต่เป็น UUID ของ Service Account User ที่ Keycloak สร้างให้โดยอัตโนมัติ และไม่มีการคืน refresh_token เพราะเครื่องจักรสามารถหมุนเวียน Access Token ได้เอง!
Day 5 Service-to-Service & Client Credentials Exam
ครอบคลุม Client Credentials Flow, Service Accounts, Zero-Downtime Rotation พร้อมข้อสอบ Spaced Repetition จาก Day 1, Day 2, Day 3 และ Day 4
Day 5 Practical Deliverable (ภารกิจประจำวัน)
ก่อนก้าวไปสู่ Days 06-07 (Checkpoint Lab #1: Entity Modeling Audit & CLI Handshake Suite) คุณต้องทำภารกิจต่อไปนี้ให้สำเร็จบน Keycloak ของคุณ: