Proof Key for Code Exchange คืออะไร

Watchanon Numnam
3 min readOct 1, 2021

PKCE (RFC 7636) เป็นส่วนต่อขยายของ Authorization Code flow เพื่อป้องกัน CSRF และ authorization code injection attacks.

normal authorization code flow

ก่อนอื่นขอเล่าถึงกระบวนการ Authorization code flow โดยปกติก่อนละกัน

  1. Client จะทำการ Request ไปยัง Authorization Server ที่ /authorize ด้วย Content-Type “application/x-www-form-urlendcoded” และมี Parameter ดังนี้
  • response_type (REQUIRED) ใน authorization code flow ใช้ค่า “code”
  • client_id (REQUIRED)
  • redirect_uri (OPTIONAL) เพื่อให้ Authorization server Redirect กลับไปที่ Client ที่ Request เข้ามา (จำค่านี้ไว้ให้ดีดี)
  • scope (OPTIONAL) เพื่อขอสิทธิ์การใช้งานต่างๆ
  • state (RECOMMENDED) เป็นค่า random ที่ Client generate เก็บไว้และส่งไปพร้อมกันการ request authorize
https://www.openapi.com/authorize?response_type=code&client_id=${clientID}&redirect_url=https://client.example.com/cb&state=xyz

2. Authorization server จะทำการ Redirect user ไปยังหน้าจอล็อกอิน เพื่อให้ User ทำการ Authentication เข้าระบบ ไม่ว่าจะเป็น Username/Password หรือใช้ QR Code แบบในไลน์เองก็ได้

3. ในส่วนนี้จะละเอาไว้ เพราะหลังจาก Authentication แล้วระบบอาจจะ Redirect ไปยังหน้า Consent เพื่อให้ User ทำการยอมรับเงื่อนไขการใช้บริการ รวมถึงดูรายละเอียดของ Scope ข้อมูลที่เราจะทำการแชร์ไปให้กับ Client

กระบวนการที่ 2 และ 3 ว่าถ้าหากเราต้องการเขียน Oauth2 server ขึ้นมาเองต้องทำยังไงเด๋วค่อยมาเขียนรายละเอียดเพิ่มไว้อีกทีละกัน ในที่นี้ขอละเอาไว้ก่อน

4. เมื่อ User ทำการ Authentication เสร็จเรียบร้อยแล้ว Authorization server จะทำการสร้าง Authorization code ส่งกลับไปให้ Client โดยส่วนใหญ่มักกำหนดให้อายุของ Authorization code นั้นมีอายุไม่เกิน 10 นาที ยิ่งน้อยยิ่งดี เพื่อป้องกันความเสี่ยง และต้องไม่สามารถใช้ซ้ำได้ หากมีการใช้งานซ้ำ Authorization ควรจะ reject request และ revoke code นั้นถ้าทำได้

หลังจากที่ Authorization server สร้าง Authorization code เสร็จแล้ว จะทำการ Redirect ค่ากลับไปที่ Client พร้อมกับส่ง Authorization code กลับไปใน Query string

https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
  • code คือ Authorization code ที่ Authorization server ส่งกลับมาให้
  • state คือค่าที่ได้รับมาจาก Client ตั่งแต่เส้นที่ (1) Request authorize /authorize
state in authorization code flow

จะเห็นว่าค่า state ที่ Client random และเก็บไว้ที่ Client พร้อมกับส่งมาที่ Authorization server นั้น ในจังหวะ Authorization server Redirect กลับไปที่ Client จะส่งกลับไปให้ Client เพื่อเช็คว่า Authorization code นี้เกิดจากการที่ Client ได้ Request มาจริงหรือไม่ ถ้าหากไม่ตรงกัน ก็ให้ Client reject code นี้ได้เลย เพราะอาจจะเกิดการ CSRF มาได้นั่นเอง ไม่ควรเอา Authorization code นี้ไปขอ Access token ในขั้นตอนต่อไป

5. เมื่อ Client ได้ Authorization code กลับมาพร้อมเช็ค state แล้วจะทำการขอ Access Token โดยการ request ไปที่ /oauth/token พร้อมกับ Parameter

  • grant_type (REQUIRE) ในที่นี้จะเป็น “authorization_code”
  • code (REQUIRE)
  • redirect_url (REQUIRE)
  • client_id(REQUIRE)
http://openapi.com/oauth/token?grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://client.example.com/cb

Authorization server จะต้องตรวจสอบว่า Authorization code นี้ถูกหรือไม่ และถูกสร้างมาสำหรับ client_id ที่ request มาในตอนแรก (1) ไหม

แล้ว PCKE มาช่วยตอนไหน

ย้อนกลับไปขั้นตอนที่ (5) อีกนิดหน่อย จริงๆแล้ว Authorization server ควรจะทำการ Client Authentication ด้วย เพื่อให้แน่ใจว่าเป็น Client ที่ถูกต้อง โดยอาจจะใช้เป็น Basic Authentication ส่งมาใน Authorization Header ในจังหวะ Request access token ก็ได้

แต่เมื่อเราต้องการทำ Public OpenAPI จริงๆแบบ LINE LIFF หรืออะไรก็ตาม กระบวนการ Client Authentication จึงอาจจะเป็นเรื่องยาก PKCE เลยเข้ามาช่วยให้ Public client น่าเชื่อถือมากขึ้น

PCKE ไม่ได้ออกแบบมาเพื่อใช้แทน client secret แต่แนะนำว่าควรใช้ถึงแม้ว่าจะมีการใช้ client secret ก็ตาม และ PCKE ก็ยังไม่ได้ออกมาเพื่อทดแทนการทำ Client Authentication แต่ทำเพื่อให้สามารถมอง “Public Client as a Confidential client” โดยเริ่มต้นมันโดนออกแบบมาเพื่อใช้ Authorization code flow สำหรับ Mobile APP (แน่นอนว่าใน Mobile App เราไม่ควรเก็บ Secret ไว้เพราะมัน Decompile ได้)

ขั้นตอนการทำงานของ PKCE

เริ่มจากการ Request authorization code (1) Client จะทำการ Generate code_verifier ขึ้นมาโดยเป็น [A-Z] ,[a-z] ,[0–9] ,“-” ,“.” ,“_” ,“~” (hyphen, period, underscore, and tilde)โดยมีความยาว 43–128 ตัวอักษร

จากนั้นจะทำการสร้าง code_challenge โดยการนำ code_verifier มาทำ SHA256 แล้วทำ Base64 Encoding ต่อ และส่งไปยัง Authorization server พร้อมกับ code_challenge_method

https://www.openapi.com/authorize?response_type=code&client_id=${clientID}&redirect_url=https://client.example.com/cb&state=xyz&code_challenge=XXXX&code_challenge_method=S256

โดยถ้าหาก Client สามารถใช้ SHA256 ได้ ก็จะส่ง code_challenge_method=S256 แต่หาก Client ไม่สามารถใช้ SHA256 ได้ก็จะสร้าง code_challenge ด้วยการ Encode code_verifier ด้วย Base64 เพียงอย่างเดียวและส่ง code_challenge_method=plain

เมื่อ Authorization server ได้รับไว้จะทำการเก็บไว้ในที่ฝั่ง server ก่อน

จากนั้นในจังหวะที่ (5) Client จะทำการขอ Access Token โดยการส่ง code_verifier เข้ามาเพิ่มจาก Flow ปกติ

http://openapi.com/oauth/token?grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://client.example.com/cb&code_verifier=YYYY

จากนั้น Authorization server จะทำการเช็คเพิ่มด้วยการนำ code_verifier ที่ได้จากเส้นที่ (5) มาใช้ SHA256 (หรือไม่ใช้จะดูจาก code_challenge_method ) และ Endcode BASE64 (กระบวนการเดียวกับที่ Client สร้าง code_challenge) ละนำมาเปรียบเทียบกับ code_challenge ที่ได้จาก Client ในเส้นที่ (1)

โดยที่กระบวนการ PKCE ทั้งหมดเป็นส่วนเสริมของ Authorization code flow โดยจะไม่มีการเปลี่ยนรูปแบบของ Response ทั้งหมด

PKCE ทำให้เราสามารถมั่นใจได้ว่า Client ที่ Authorization request (1) มา เป็น Client ตัวเดียวกับที่ทำการขอ Token ในขั้นตอนที่ (5) เพื่อป้องกันนำ Authorization code จาก Victim Client ตัวอื่น ไปใช้กับ Attacker Client อีกตัวหนึ่งนั่นเอง (Authorization code injection attack) เพื่อทำให้ Attacker client ได้รับ Access Token ของเหยื่อ

--

--