Animated Architecture Patterns

OAuth 2.0 Client Credentials

access token สำหรับงาน machine-to-machine ที่ไม่มีผู้ใช้มาเกี่ยวเลย

หรือที่เรียกกันว่า Client credentials grant, Machine-to-machine (M2M) authorization, Two-legged OAuth

POST /token with id + secret with a signed JWT 200 OK access token GET /stock/42 · Authorization: Bearer <access token> Orders service the OAuth client credential store client_id orders-svc client_secret •••••••• private key never sent token cache in memory empty nothing cached yet access_token eyJ… reused until it nears expiry Any unattended workload: a service, a nightly job, a queue worker, a daemon API calls token requests 0 100 200 300 400 500 600 1 2 Authorization server https://idp.example POST /token token endpoint Authorization: Basic base64(orders-svc:••••••••) grant_type=client_credentials scope=inventory:read client_assertion=eyJhbGciOi… a JWT the client signed client_assertion_type=urn:…:jwt-bearer Registered clients client_id authenticates with may request orders-svc client_secret_basic private_key_jwt inventory:read billing-job tls_client_auth invoices:write Nobody signs in: the service asks as itself. Confidential clients only: software that can keep a credential to itself. no redirect no sign-in page no consent screen 200 OK token response access_token eyJhbGciOi… token_type Bearer expires_in 3600 refresh_token none: ask again sub is the client itself, not a person Access token a signed JWT sub orders-svc client_id orders-svc aud inventory-api scope inventory:read Token lifetime expires_in 3600 renew issued expires time-lapse: each dot is 100 API calls close to expiry: ask again new token before the old one ends The same cached token rides on every call. No token request per call. Stronger than a stored shared secret: private_key_jwt · a signed JWT assertion, the one shown here The client signs a short-lived JWT; the server stores only its public key. tls_client_auth · a client certificate, mutual TLS The TLS handshake proves the client holds the certificate’s private key. workload identity · no stored credential at all The platform issues a short-lived token, exchanged for an access token. Inventory API resource server Checks the token locally signature (cached JWKS) iss, aud, exp scope inventory:read Caller, from the token: client_id orders-svc stock data the protected resource 200 OK

ขั้นที่ 1 · ขอ token ในนามตัวเอง backend service ต้องเรียก API ในนามของตัวเอง: ในตัวอย่างนี้คือ Orders service ที่เรียก Inventory API แต่จะเป็น billing job ที่รันทุกคืนก็ได้เหมือนกัน service ส่ง POST /token พร้อม grant_type=client_credentials และ scope=inventory:read ตรงไปที่ authorization server แล้วยืนยันตัวตนในนามตัวเอง ตัวอย่างนี้ใช้ client_id กับ client_secret ใส่ไว้ใน HTTP Basic header ไม่มีใครต้องเข้าสู่ระบบ เลยไม่มี redirect ไม่มีหน้าเข้าสู่ระบบ และไม่มีหน้า consent ส่วน grant แบบนี้ใช้ได้กับ confidential client เท่านั้น คือซอฟต์แวร์ที่เก็บ credential เป็นความลับไว้กับตัวเองได้

ขั้นที่ 2 · server ออก token ให้ authorization server ยืนยันตัวตน client เทียบกับข้อมูลที่ลงทะเบียนไว้ แล้วดูว่า client ตัวนี้มีสิทธิ์ขอ scope ที่ขอมาไหม จากนั้นก็คืน access token พร้อม token_type และ expires_in โดยไม่มี refresh token: พอ token หมดอายุ client ก็แค่ยืนยันตัวตนใหม่อีกรอบ subject ของ token คือตัว client เอง (sub และ client_id เป็น orders-svc ทั้งคู่) ไม่ใช่คน

ขั้นที่ 3 · เรียก API แล้วใช้ token ซ้ำ service เรียก Inventory API ด้วย Authorization: Bearer … แล้ว API ก็ตรวจ token เองในเครื่อง: signature, issuer, audience, เวลาหมดอายุ และ scope ส่วน service เองก็เก็บ token ไว้ใน memory แล้วส่งตัวเดิมไปกับทุกการเรียก พอใกล้หมดอายุค่อยขอตัวใหม่ service ไม่ขอ token ใหม่ทุกครั้งที่เรียก เพราะจะเพิ่ม round trip ให้ทุก request และไปชน rate limit ของ token endpoint

ขั้นที่ 4 · เลิกใช้ shared secret shared secret ต้องเก็บไว้ที่ client แถมยังวิ่งผ่าน network ทุกครั้งที่ขอ token และใครคัดลอกไปก็ใช้ได้ เปลี่ยนไปใช้ของที่ไม่มีทางรั่วแบบเดียวกันแทน: JWT assertion ที่ sign ด้วย private key (private_key_jwt ตามภาพ), client certificate (mutual TLS) หรือ workload identity ของ platform คือ token อายุสั้นที่ platform ออกให้ workload แล้วเอาไปแลกเป็น access token แบบนี้ก็ไม่ต้องเก็บอะไรที่อายุยาวไว้เลย

ปัญหา

caller ไม่ได้เป็นคนเสมอไป ตัว service ที่เรียก service อื่น, billing job ที่รันทุกคืน, queue worker หรือ deployment pipeline ไม่มีผู้ใช้ให้พาไปผ่านหน้า sign-in แต่ API ที่ปลายทางก็ยังต้องรู้ว่าใครเป็นคนเรียก และ caller ตัวนั้นทำอะไรได้บ้าง

ทางลัดที่ใช้กันบ่อยมักเสื่อมตามเวลา static API key ไม่เคยหมดอายุ วิ่งไปกับทุก request และสุดท้ายก็ถูกก็อปไปไว้หลายที่จนไม่มีใครจำได้ "service user" ที่มี password แล้วถูกยัดเข้า flow ที่ออกแบบมาให้คนใช้ ก็จะติด MFA และบังไว้ว่าระบบไหนเป็นคนเรียกจริง ๆ ส่วนการเชื่อทุกอย่างที่มาจากใน network ก็ใช้ได้แค่จนกว่าจะมี workload สักตัวโดนเจาะ สิ่งที่ caller ต้องมีคือ identity ของตัวเอง วิธีพิสูจน์ identity นั้น และ token อายุสั้นที่ scope แคบ ที่ API ตรวจได้เหมือน access token ตัวอื่น ๆ

ทำงานยัง⁠ไง

client credentials grant (RFC 6749 §4.4) เป็น flow ที่สั้นที่สุดของ OAuth 2.0: request ไปที่ token endpoint หนึ่งครั้งและ response กลับมาหนึ่งครั้ง ไม่มี authorization endpoint ไม่มี redirect และไม่มี authorization code เพราะไม่มีใครถูกขอ consent ตอน request: client ทำในนามตัวเอง หรือภายใต้ข้อตกลงที่ทำไว้ล่วงหน้ากับ authorization server การ authenticate client คือการ authorize ทั้งหมดแล้ว

  1. Token request client ส่ง POST ไปที่ token endpoint พร้อม body แบบ form-encoded: grant_type=client_credentials และ scope ถ้าต้องการ มัน authenticate ใน request เดียวกันนี้เลย
  2. Token response authorization server authenticate client ตัดสินว่า client ตัวนี้ควรได้อะไร แล้วคืน access_token, token_type และปกติก็มี expires_in มาด้วย
  3. API call client ส่ง Authorization: Bearer <token> ไป แล้ว API ก็ validate token (ดู JWT Validation) และ authorize request จาก claim ของมัน
  4. ใช้ซ้ำ แล้วค่อยต่ออายุ client เก็บ token ไว้ใน memory ส่งไปกับทุก call และทำขั้นที่ 1 ซ้ำก่อน token จะหมดอายุไม่นาน

client คือใคร client ถูก register ไว้กับ authorization server ล่วงหน้า: มี client_id, วิธี authenticate และ scope หรือ API ที่มันขอได้ ตัว grant นี้มีไว้ให้ confidential client เท่านั้น (RFC 6749 §4.4 และใน draft ของ OAuth 2.1 ก็ไม่เปลี่ยน): ซอฟต์แวร์ที่รันอยู่ในที่ที่เก็บ credential ให้พ้นมือผู้ใช้ได้ เช่น server, job หรือ daemon ส่วน single-page app หรือ mobile app เก็บ credential ไม่ได้ เลยใช้ grant นี้ไม่ได้

มันพิสูจน์ตัวเองยัง⁠ไง วิธีข้างล่างมาจาก registry OAuth Token Endpoint Authentication Methods ของ IANA (ที่มี none สำหรับ public client อยู่ด้วย) บวก workload identity federation เข้าไป เรียงคร่าว ๆ จากอ่อนสุดไปแข็งสุด:

วิธี client ถืออะไรไว้ อะไรถูกส่งไปที่ token endpoint
client_secret_basic, client_secret_post (RFC 6749 §2.3.1) shared secret ตัว secret เอง ใน HTTP Basic header หรือใน form body
client_secret_jwt (OpenID Connect Core §9) shared secret JWT อายุสั้นที่ป้องกันด้วย HMAC ที่ใช้ secret เป็น key: secret ไม่ต้องวิ่งบนสาย แต่ทั้งสองฝั่งก็ยังรู้ secret อยู่ดี
private_key_jwt (RFC 7523, OpenID Connect Core §9) private key JWT อายุสั้นที่ sign ด้วย key นั้น ใส่ไว้ใน client_assertion ฝั่ง server ถือแค่ public key
tls_client_auth, self_signed_tls_client_auth (RFC 8705) private key กับ X.509 certificate ไม่มีอะไรนอกจาก client_id: TLS handshake พิสูจน์ว่าถือ key อยู่จริง แล้ว server ก็จับคู่ certificate กับ client
Workload identity federation ไม่มีอะไรที่อายุยาว token อายุสั้นที่ platform ออกให้ workload โดยส่งไปเป็น client_assertion หรือเป็น subject token ของ token exchange

RFC 9700 ที่เป็น OAuth security best current practice แนะนำวิธีแบบ asymmetric คือ mutual TLS หรือ private_key_jwt: authorization server จะได้ไม่มี symmetric secret ให้ต้องเก็บ ทำให้ของที่รั่วจากฝั่ง server เอาไปปลอมเป็น client ไม่ได้

token บอกอะไร พอไม่มีผู้ใช้มาเกี่ยว ตัว token ก็เลยพูดถึง client โดยใน JWT access token profile (RFC 9068) ค่า sub ควรเป็น identifier ที่ authorization server ใช้เรียก client application และมี client_id อยู่เสมอ ส่วน aud บอกชื่อ API และ scope บอกว่า client ทำอะไรที่นั่นได้บ้าง

ทำไมไม่มี refresh token refresh token ให้ client ขอ access token ใหม่ได้โดยไม่ต้องพาผู้ใช้ไปเดิน flow ใหม่ แต่ตรงนี้ไม่มีใครต้องถูกพาไปไหน: client authenticate ใหม่ได้ทุกเมื่อที่ต้องการ ตัว refresh token เลยเป็นแค่ secret อายุยาวอีกตัวที่ต้องเก็บ RFC 6749 §4.4.3 เขียนไว้เป็น "SHOULD NOT" และไม่ได้บอกเหตุผล แล้ว server ในทางปฏิบัติก็ทำตาม: Microsoft Entra ID ไม่เคยออก refresh token ใน flow นี้ ส่วน Keycloak ก็ไม่ออกให้ ยกเว้นจะเปิด switch ที่ deprecated แล้ว

ใช้ตอนไหนดี

  • Service-to-service call ที่ caller ทำในอำนาจของตัวเอง: Orders อ่าน stock level หรือ reporting service ดึงข้อมูลจาก billing API
  • Scheduled job, batch run, queue worker และ daemon: อะไรก็ตามที่รันเองโดยไม่มีคนเฝ้า
  • Automation: CI/CD pipeline, operator และ infrastructure tooling ที่เรียก management API
  • ไม่ใช่สำหรับอะไรที่ทำแทนผู้ใช้ ถ้ามีคนอยู่เบื้องหลัง request ให้ใช้ user flow อย่าง OAuth 2.0 Authorization Code + PKCE เพื่อให้ token มี identity และ consent ของผู้ใช้คนนั้น ส่วน service ที่อยู่กลาง request ของผู้ใช้และต้องเรียก downstream API ในนามผู้ใช้คนนั้น ควรแลก token ที่ได้รับมา (OAuth 2.0 Token Exchange, RFC 8693 หรือ pattern on-behalf-of) แทนที่จะเปลี่ยนไปใช้ client credentials ของตัวเอง ไม่อย่างนั้นผู้ใช้จะหายไปจาก audit trail และ permission กว้าง ๆ ของ service ก็จะมาแทนที่สิ่งที่ผู้ใช้ได้รับอนุญาตจริง
  • ไม่ใช่สำหรับ public client browser, mobile app และ desktop app เก็บ client credential ไม่ได้

ได้อะไร เสียอะไร

  • client credential คือสิ่งที่ต้องปกป้อง ใครถือมันก็ขอ token ในนาม service ได้ ตอนไหนก็ได้ จากที่ไหนก็ได้ shared secret เป็นแบบที่อ่อนที่สุด: มันอยู่ใน configuration, pipeline และบ่อยเกินไปก็อยู่ใน source control มันถูกส่งไปทั้งก้อนทุกครั้งที่ขอ token และตัวที่ก็อปไปก็ใช้ได้ดีเท่าตัวจริง วิธีแบบ asymmetric เก็บ private key ไว้ฝั่ง client ส่วน workload identity ไม่เหลือ credential ที่ต้องเก็บไว้เลย
  • ไม่มีผู้ใช้ก็ไม่มีขอบเขตแบบรายผู้ใช้ token จาก grant นี้มี permission ระดับ application: inventory:write แปลว่า inventory ทั้งหมด ไม่ใช่ของลูกค้าคนเดียว least privilege เลยต้องมาจากตอน register: หนึ่ง client ต่อหนึ่ง workload, scope ที่แคบที่สุดที่ยังใช้งานได้ และ token ที่จำกัดไว้ audience เดียว (RFC 9700 §2.3)
  • bearer token ใช้ได้กับใครก็ตามที่ถือมัน token ที่รั่วจาก log หรือจาก hop ที่โดนเจาะเอาไป replay ได้จนกว่าจะหมดอายุ ให้ตั้งอายุสั้น ๆ และสำหรับ API ที่มีมูลค่าสูง ให้ผูก token ไว้กับ key ที่มีแค่ client ถือ: certificate-bound token ผ่าน mutual TLS (RFC 8705, cnf.x5t#S256) หรือ DPoP (RFC 9449, cnf.jkt) ตัว RFC 9700 แนะนำ sender-constrained access token
  • revoke ได้แบบหยาบ ๆ ไม่มี session ให้ปิด การ disable client หรือ rotate credential ของมันหยุดการออก token ใหม่ได้ แต่ token ที่ออกไปแล้วยังผ่าน local validation ไปจนถึง exp
  • token endpoint กลายเป็น dependency token ที่ cache ไว้พาผ่านช่วงที่ authorization server ล่มสั้น ๆ ได้ แต่ client ที่ขอ token ทุกครั้งที่เรียกจะทำให้ทุกอาการสะดุดและทุก rate limit กลายเป็น request ที่ fail
  • client กับผู้ใช้สับสนกันได้ ถ้า client ID กับ user ID ใช้ namespace เดียวกัน API ก็อาจเข้าใจ sub ของ client ว่าเป็นของผู้ใช้ ตัว RFC 9700 §4.15 บอก authorization server ว่าในกรณีนั้นอย่าให้ client เลือก client_id เอง หรือไม่ก็ให้ API มีวิธีอื่นแยก token สองแบบนี้ออกจากกัน
  • workload identity ย้ายความไว้ใจไปที่อื่น ไม่ได้ลบมันทิ้ง ตอนนี้ authorization server ไว้ใจ token issuer ของ platform และ subject string อย่าง Kubernetes service account หรือ repository กับ branch อะไรก็ตามที่รันภายใต้ subject นั้นได้ก็จะได้ token เพราะฉะนั้น trust rule ต้องปักหมุด issuer, subject และ audience ไว้ให้ตรงเป๊ะ

ข้อควรรู้ตอนลงมือทำ

  • Request body เป็นแบบ form-encoded (application/x-www-form-urlencoded) ไม่ใช่ JSON มันวิ่งผ่าน TLS โดยไม่มี credential อยู่ใน URL เด็ดขาด และใช้วิธี authenticate แค่วิธีเดียวต่อ request ถ้าใช้ HTTP Basic ตัว RFC 6749 §2.3.1 ให้ client form-urlencode ID กับ secret ก่อนเอามาต่อกันแล้ว encode เป็น Base64 ขั้นนี้ implementation ข้ามกันบ่อยจน draft ของ OAuth 2.1 (draft 16 เดือนกันยายน 2026 ที่ยังเป็นเอกสารของ working group) กลับ default: server ต้องรับ credential ใน form body และจะรับ Basic ด้วยก็ได้ เช็กว่า server ของเราคาดหวังแบบไหน
  • Error client authentication ที่ fail คือ invalid_client (HTTP 401 ถ้า client ใช้ header Authorization) ถ้าขอ scope ที่ client ไม่มีสิทธิ์จะได้ invalid_scope และ client ที่ไม่ได้รับอนุญาตให้ใช้ grant นี้จะได้ unauthorized_client (RFC 6749 §5.2) ทั้งสามตัวเป็นปัญหา configuration เพราะฉะนั้นให้ตั้ง alert แทนที่จะ retry
  • Cache token ใช้ authorization server, client, audience และ scope เป็น key ของ cache ใช้ token ซ้ำไปจนก่อน expires_in จะหมดไม่นาน โดยเผื่อ margin ไว้ครอบ clock skew และ request ที่ยังวิ่งอยู่ ทำให้การต่ออายุเป็นแบบ single-flight การเรียกที่มาเป็นชุดจะได้ทำให้เกิด token request แค่ครั้งเดียว ไม่ใช่ร้อยครั้ง และถ้า API ตอบ 401 กับ token ที่ cache ไว้ ให้ทิ้งมันแล้วขอตัวใหม่หนึ่งครั้ง OAuth client library ส่วนใหญ่ทำเรื่องนี้ให้: อย่าง AcquireTokenForClient ของ MSAL ก็เช็ก application token cache ก่อนเรียก token endpoint ถ้ามีหลาย replica ให้แชร์ cache หรือกระจายจังหวะการต่ออายุออกไป
  • อายุและข้อจำกัด อายุที่สั้นลงทำให้ช่วงที่ replay ได้แคบลง แต่ก็เพิ่มอัตรา token request ส่วนอายุหนึ่งชั่วโมงเป็น default ที่ใช้กันบ่อย (Microsoft Entra ID ให้ token แต่ละตัวมีอายุ default 60 ถึง 90 นาที) ตัว token endpoint มี rate limit และบางที่ก็คิดเงินตามการใช้: Entra ID throttle client ที่ขอถี่เกินไป ส่วน Amazon Cognito คิดเงินการใช้แบบ machine-to-machine ตามจำนวน app client ที่ active และจำนวน token request และแนะนำให้ cache เป็นวิธีลดค่าใช้จ่ายนี้
  • Scope, audience และ resource ขอ token หนึ่งตัวต่อหนึ่ง API วิธีมาตรฐานในการระบุ API คือ parameter resource (RFC 8707) ถ้าไม่มี parameter นี้ RFC 9068 ให้ server หา aud จาก scope ที่ขอมา และ server ควรปฏิเสธ request ที่ scope ชี้ไปหลาย API แต่ละ vendor ทำต่างกัน: Auth0 รับ parameter audience ที่เป็น identifier ของ API ส่วน Microsoft Entra ID รับ scope=<resource>/.default แล้วใส่ application permission ที่ administrator ให้ไว้ลงใน claim roles และ Cognito ออกให้แค่ custom scope (resource-server/scope) ที่ผูกกับ app client และไม่รับ resource สำหรับ grant นี้
  • ใน API authorize จาก identity ของ client (client_id หรือ sub) คู่กับ scope หรือ role และต้องรู้ให้ได้ก่อนว่า token เป็นของ client หรือของผู้ใช้ แล้วค่อยเชื่อ sub ไม่มีอะไรใน token บอกว่าคนไหนเป็นต้นเหตุของการเรียก (ถ้ามี) ถ้า API ต้องรู้เรื่องนั้น แปลว่าใช้ grant ผิดตัว
  • private_key_jwt assertion คือ JWT ที่ตั้ง iss และ sub เป็น client ID มี aud ระบุ authorization server มี exp สั้น ๆ และมี jti ที่ไม่ซ้ำ (RFC 7523, OpenID Connect Core §9) ส่งไปเป็น client_assertion คู่กับ client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer ถ้า server รองรับ ให้ register jwks_uri แทน public key แบบตายตัว การ rotate key จะได้ไม่ต้องแก้อะไรที่ server ส่วน private key ให้เก็บไว้ใน KMS หรือ HSM ที่ sign ให้ตามคำขอ การ update RFC 7523 (draft-ietf-oauth-rfc7523bis ที่ approve แล้วและอยู่ในคิวของ RFC Editor ณ ตุลาคม 2026) ปิดช่อง audience-confusion attack: aud ต้องเป็น issuer identifier ของ authorization server เท่านั้น ไม่ใช่ URL ของ token endpoint อีกต่อไป และ JWT ควรมี type เป็น client-authentication+jwt ผลิตภัณฑ์ต่าง ๆ ยังตามไม่ทัน (อย่างคู่มือ service app ของ Okta ก็ยังใช้ URL ของ token endpoint เป็น aud) เพราะฉะนั้นให้ทำตามเอกสารของ server ที่ใช้
  • Mutual TLS ตัว tls_client_auth validate certificate chain และจับคู่กับ subject ที่ register ไว้หนึ่งตัว (DN หรือ SAN) ทำให้ certificate ที่ต่ออายุแล้วแต่ subject เดิมไม่ต้องแก้อะไรที่ server ส่วน self_signed_tls_client_auth ข้ามการตรวจ chain และจับคู่ certificate กับตัวที่ register ไว้ใน JWKS ของ client ถ้า TLS ไปจบที่ load balancer ตัว certificate ต้องไปถึง authorization server ในรูปแบบที่มันไว้ใจได้ และ RFC 8705 ปล่อยเรื่องนี้ไว้ให้แต่ละ deployment จัดการเอง
  • Workload identity federation platform ที่รัน workload รู้อยู่แล้วว่ามันคืออะไร และบอกเรื่องนั้นได้ใน signed token อายุสั้น: Kubernetes projected service-account token (ผูกกับ audience และ pod และ kubelet จะเปลี่ยนให้ใหม่เมื่อถึง 80% ของอายุหรือหลัง 24 ชั่วโมง เลยต้องอ่านไฟล์ใหม่) SPIFFE SVID จาก Workload API (X.509 certificate หรือ JWT ที่ได้มาโดยไม่ต้องมี bootstrap secret) หรือ token ของ cloud provider (workload บน AWS ขอ JWT จาก STS GetWebIdentityToken ได้) authorization server จะถูกตั้งให้ไว้ใจ issuer นั้นสำหรับ subject และ audience หนึ่งชุด แล้ว workload ก็ยื่น token นั้นในตำแหน่งที่เคยต้องใช้ secret ส่วน Microsoft Entra ID จะรับมันเป็น client_assertion ของ client credentials request ธรรมดา โดยจับคู่กับ federated identity credential บนแอป ส่วน Google Cloud แลกมันที่ Security Token Service ตาม OAuth 2.0 Token Exchange (RFC 8693) แล้ว Keycloak 26.8 ก็รับ Kubernetes service-account token เป็น client assertion ได้ (feature นี้เปิดเป็น default) และรับ SPIFFE JWT-SVID ได้ในฐานะ preview feature ตอนนี้มี working-group draft ของ IETF ชื่อ OAuth SPIFFE Client Authentication ที่กำลังทำกรณี SPIFFE ให้เป็นมาตรฐาน
  • ถ้ายังใช้ secret อยู่ ให้ authorization server เป็นคน generate แล้วเก็บไว้ใน secret manager (ห้ามเก็บใน source, image หรืออะไรที่ส่งไปถึงผู้ใช้) และให้ทุก workload มี client ของตัวเอง ถ้ารั่วจะได้จำกัดวงและตามรอยได้ rotate ตามรอบโดยให้มี credential สองตัวที่ใช้ได้พร้อมกัน: Cognito ให้มี active secret สองตัวต่อ app client และ Auth0 ให้ register public key ได้สองตัว ตัวใหม่เลย roll out ได้ก่อนจะลบตัวเก่า ตั้ง alert ก่อน secret หรือ certificate จะหมดอายุ: credential ที่หมดอายุก็คือ outage
  • ผลิตภัณฑ์ authorization server หลัก ๆ ทุกตัวรองรับ grant นี้ ที่ต่างกันคือจะใช้อะไรแทน secret ได้บ้าง Microsoft Entra ID รับ secret, assertion ที่ sign ด้วย key ของ certificate หรือ federated credential และเอกสาร MSAL ของมันก็ระบุว่าไม่แนะนำให้ใช้ secret ใน production ส่วน Auth0 รองรับ client secret, Private Key JWT (Enterprise plan) และ mutual TLS (Enterprise plan พร้อม add-on Highly Regulated Identity) ด้าน management API ของ Okta เองรับแค่ private_key_jwt จาก service app ส่วน Amazon Cognito authenticate app client ด้วย client secret ที่ token endpoint หรือผ่าน API GetClientToken ของมัน แล้ว Keycloak ก็เปิด grant นี้ราย client ด้วย "Service accounts roles" และ authenticate client ได้ด้วย secret, signed JWT หรือ X.509 certificate
  • ตรวจ token ที่ไหน API gateway validate token ได้ก่อนที่ request จะไปถึง service ส่วนใน service mesh ตัว mutual TLS บอกอยู่แล้วว่า workload ไหนอยู่ปลายอีกฝั่งของ connection แต่ access token เพิ่ม scope และ audience เข้ามา และยังมีความหมายอยู่แม้ request จะผ่าน gateway หรือ proxy ที่ terminate TLS ไปแล้ว