API key กับ OAuth แก้ปัญหาคนละเรื่องกัน และการเลือกระหว่างสองอย่างนี้เริ่มจากคำถามเดียว: ในภาพนี้มีบุคคลที่สามหรือไม่
ถ้าเซิร์ฟเวอร์ของคุณเรียก API ของคนอื่นในนามของตัวเอง คุณต้องใช้ key ถ้าแอปของคนอื่นเรียก API ของคุณในนามผู้ใช้ของคุณ คุณต้องใช้ OAuth เมื่อไม่มีบุคคลที่สาม OAuth เพิ่มพิธีการเข้ามาโดยไม่ได้เพิ่มความปลอดภัย
ต่อจากนี้: ทั้งสองต่างกันตรงไหนจริง ๆ, OAuth เปลี่ยนอะไรไปบ้างในไม่กี่ปีที่ผ่านมา และคุณสมบัติหนึ่งของ API key ที่แทบไม่มีใครเขียนถึง ทั้งที่มันเป็นตัวกำหนดว่าคุณจะอยู่กับมันอย่างไรบนระบบจริง
API key: สายอักขระหนึ่งบรรทัดที่บอกว่า "โปรแกรมนี้ได้รับอนุญาต"
key คือสายอักขระยาวและสุ่ม ที่คุณใส่ไว้ในส่วนหัวของคำขอ
X-Api-Key: lix_live_9f2c...
เซิร์ฟเวอร์หา key เจอ รู้ว่าเป็นของบัญชีใด แล้วปล่อยคำขอผ่านไป กลไกทั้งหมดมีเท่านี้
คุณสมบัติที่ตามมาจากตรงนั้น
- key ไม่หมดอายุด้วยตัวเอง มันอยู่ไปจนกว่าจะมีคนเพิกถอน
- key ระบุตัวโปรแกรม ไม่ใช่ตัวคน ใครในทีมเป็นคนเรียกนั้นตามปกติมองไม่เห็น
- key อนุญาตทุกอย่างที่บัญชีทำได้ เว้นแต่คุณจำกัดสิทธิ์ไว้ต่างหาก
- key คือความลับเพียงอย่างเดียว รั่วเมื่อไร สิทธิ์เข้าถึงก็รั่วเมื่อนั้น ไม่มีขั้นกลาง
OAuth: ข้อตกลงว่าด้วยการยินยอม
OAuth 2.0 (RFC 6749) ตอบคำถามอีกแบบ มีผู้เกี่ยวข้องสี่ฝ่าย ได้แก่ เจ้าของข้อมูล แอปที่อยากได้ข้อมูล เซิร์ฟเวอร์ให้สิทธิ์ และเซิร์ฟเวอร์ที่เก็บข้อมูลไว้
ลำดับคร่าว ๆ เป็นแบบนี้ แอปส่งผู้ใช้ไปยังหน้าเว็บของบริการนั้นเอง ผู้ใช้เห็นว่าใครขออะไร แล้วกดยินยอม แอปได้รับ code นำไปแลกเป็น access token แล้วเรียก API ด้วย token นั้น token มีอายุสั้น เป็นนาทีหรือชั่วโมง เมื่อหมดอายุ แอปก็เอา refresh token ไปแลกอันใหม่
สิ่งที่วิธีนี้ให้ และ key ให้ไม่ได้โดยหลักการ
- รหัสผ่านของผู้ใช้ไม่เคยไปถึงแอป ความหมายทั้งหมดอยู่ตรงนี้
- สิทธิ์ถูกซอยเป็น scope "อ่านลิงก์" กับ "ลบลิงก์" เป็นการอนุญาตคนละใบ
- การยินยอมมองเห็นได้และถอนคืนได้ ผู้ใช้เห็นรายชื่อแอปที่เชื่อมต่ออยู่ และตัดออกทีละแอปได้โดยไม่กระทบที่เหลือ
- token หมดอายุเอง access token ที่ถูกขโมยไป อีกหนึ่งชั่วโมงก็ไร้ค่า
ราคาที่จ่ายคือความซับซ้อน ต้องมีเซิร์ฟเวอร์ให้สิทธิ์ การลงทะเบียนแอป หน้าจอยินยอม การเก็บและหมุน refresh token และการรับมือเรื่องหมดอายุในทุกการเรียก
เปรียบเทียบตามแกนที่ตัดสินจริง
| API key | OAuth 2.0 | |
|---|---|---|
| รับรองใคร | โปรแกรม | ผู้ใช้ที่โปรแกรมทำการแทน |
| อายุการใช้งาน | ไม่มีกำหนด | access token ระดับนาทีถึงชั่วโมง |
| ตัดสิทธิ์อย่างไร | เพิกถอน key | ถอนการยินยอมหรือ token |
| ขอบเขตสิทธิ์ | โดยทั่วไปคือทั้งบัญชี | scope แยกตามการกระทำ |
| ความลับที่อยู่กับบุคคลที่สาม | ตัว key เอง | เฉพาะ token ไม่เคยเป็นรหัสผ่าน |
| แรงที่ต้องลงในการเชื่อมต่อ | ส่วนหัวอันเดียว | เซิร์ฟเวอร์ให้สิทธิ์และวงจรทั้งชุด |
| เหมาะกับ | เซิร์ฟเวอร์ถึงเซิร์ฟเวอร์ สคริปต์ การเชื่อมต่อภายใน | แอปสาธารณะ มาร์เก็ตเพลส "เข้าสู่ระบบด้วย…" |
กฎในการเลือก
คำถามไม่ใช่ว่าอย่างไหนปลอดภัยกว่าในเชิงนามธรรม แต่คือใครกำลังมอบสิทธิ์เข้าถึงให้ใคร
เซิร์ฟเวอร์ของคุณ → API ของคนอื่น บัญชีเป็นของคุณ
→ API key
แอปของคนอื่น → API ของคุณ บัญชีเป็นของผู้ใช้คุณ
→ OAuth
งานตามกำหนดเวลา CI การเชื่อมต่อฝั่งหลังบ้าน
→ API key
ผู้ใช้ต้องเห็นและถอนสิทธิ์เป็นรายแอป
→ OAuth
การรับ postback คือกรณีตัวอย่างในตำราของ key เมื่อเซิร์ฟเวอร์ของคุณแจ้งเซิร์ฟเวอร์ของเราว่าคำสั่งซื้อชำระเงินแล้ว ในการแลกเปลี่ยนนั้นไม่มีผู้ใช้อยู่เลยสักคน ไม่มีใครให้ยินยอม และไม่มีที่ทางจะวางหน้าจอยินยอม webhook ในทิศทางกลับกันก็เช่นเดียวกัน
ตอนที่คุณไม่ได้มอง OAuth เปลี่ยนไปแล้ว
บทความครึ่งหนึ่งในหัวข้อนี้อธิบาย OAuth แบบปี 2015 หลังจากนั้นมันแคบลงไม่น้อย
มกราคม 2025 IETF เผยแพร่ RFC 9700, Best Current Practice for OAuth 2.0 Security เอกสารนี้รวบรวมประสบการณ์จากการโจมตีจริงหลายปีไว้ด้วยกัน และประกาศเลิกใช้อย่างเป็นทางการสองวิธีที่เคยถือว่ายอมรับได้
- implicit grant — วิธีที่คืน token มาในแถบที่อยู่ตรง ๆ
- resource owner password credentials — ที่แอปถามชื่อผู้ใช้และรหัสผ่านจากผู้ใช้โดยตรง ซึ่งคือสิ่งที่ OAuth เกิดมาเพื่อกำจัดพอดี
พร้อมกันนั้น PKCE กลายเป็นข้อบังคับสำหรับไคลเอนต์ทุกชนิด รวมถึงฝั่งเซิร์ฟเวอร์ด้วย ไม่ใช่เฉพาะฝั่งมือถืออย่างแต่ก่อน
อีกเรื่องที่ควรรู้แยกต่างหาก: OAuth 2.1 จนถึงตอนนี้ยังเป็นร่าง ไม่ใช่ RFC ที่เผยแพร่แล้ว มันรวบการเปลี่ยนแปลงชุดเดียวกันไว้ในเอกสารเดียว ได้แก่ PKCE บังคับ การเทียบ redirect URI แบบตรงตัว การเลิก implicit และ password grant และการห้ามวาง token ไว้ในสตริงคำค้น การอ้างถึงมันในฐานะมาตรฐานที่ใช้อยู่จึงยังเร็วเกินไป
ข้อสรุปเชิงปฏิบัติ ถ้าคู่มือ OAuth ที่คุณอ่านยังแนะนำ implicit flow แปลว่าคู่มือนั้นเก่าไปแล้ว
จุดอ่อนของ key ที่แทบไม่มีใครพูดถึง
key ไม่หมดอายุ นั่นทำให้การเพิกถอนเป็นคันโยกเดียวที่คุณมี แล้วตรงนี้เองที่เรื่องน่าอึดอัดโผล่ขึ้นมา: การเพิกถอนมักไม่เกิดผลทันที
การตรวจ key ทุกคำขอหมายถึงการวิ่งไปถามฐานข้อมูลทุกครั้ง API ใดก็ตามที่มีปริมาณใช้งานจริงย่อมแคชผลนั้นไว้ ที่เรา การจับคู่ระหว่าง key กับบัญชีอยู่ในแคชห้านาที ดังนั้นระหว่างการกด "เพิกถอน" กับการที่สิทธิ์ถูกตัดจริงจึงห่างกันได้ถึงห้านาที และในช่วงนั้น key ที่หลุดออกไปยังทำงานได้ตามปกติ
นี่ไม่ใช่ความบกพร่อง แต่เป็นการแลก ถ้าไม่มีแคช ทุกคำขอต้องไปเคาะประตูฐานข้อมูล API ที่ใช้ key แทบทุกเจ้าก็ทำข้อตกลงคล้าย ๆ กัน เพียงแต่ไม่ค่อยมีใครพูดออกมาดัง ๆ ที่ต้องรู้ไว้มีสองเหตุผล หนึ่ง เมื่อเกิดการรั่วไหลจริง การเพิกถอน key คือการกระทำแรก ไม่ใช่การกระทำสุดท้าย หลังจากนั้นต้องไปตรวจว่าช่วงไม่กี่นาทีนั้นเกิดอะไรขึ้นบ้าง สอง นี่คือปัญหาเดียวกับที่ token อายุสั้นของ OAuth แก้จากอีกด้านหนึ่ง คือมันหมดอายุเองโดยไม่ต้องพึ่งฐานข้อมูล
อยู่กับ key อย่างไรไม่ให้เจ็บตัว
ถ้า key เพียงพอ และในการเชื่อมต่อส่วนใหญ่ก็เพียงพอ ขั้นต่ำคือแบบนี้
- เก็บเฉพาะค่าแฮช เซิร์ฟเวอร์นำแฮชของ key ที่ส่งมาไปเทียบกับที่เก็บไว้ ส่วนสายอักขระต้นฉบับจะแสดงให้ผู้ใช้เห็นครั้งเดียวตอนสร้าง ของเราเทียบด้วย SHA-256
- ใส่คำนำหน้าให้ key สายอักขระรูปแบบ
lix_live_...เป็นสิ่งที่ตัวสแกนความลับในคลังโค้ดและในสายงานบันทึกจดจำได้ - หนึ่ง key ต่อหนึ่งการเชื่อมต่อ เพิกถอนอันหนึ่งแล้วอันอื่นไม่ล้มตาม และจากบันทึกจะเห็นชัดว่าอันไหนหลุดไป
- อย่าใส่ key ลงในสตริงคำค้นเด็ดขาด ที่อยู่จะไปตกค้างในบันทึกของเว็บเซิร์ฟเวอร์ ในส่วนหัว
Refererและในประวัติเบราว์เซอร์ ใช้ส่วนหัวอย่างเดียว - หมุนตามปฏิทิน ไม่ใช่ตามเหตุการณ์ key ที่เคยหมุนสักครั้งจะหมุนได้เร็ว ส่วน key ที่ไม่เคยเปลี่ยนเลย สุดท้ายจะพบว่าฝังแน่นอยู่สี่ที่
- ดูเวลาใช้งานล่าสุด key ที่ไม่มีใครแตะมาครึ่งปีไม่ใช่ของสำรอง แต่เป็นประตูที่เปิดค้างไว้
เราใช้อะไร
API ของ Lix.li ยืนยันตัวตนด้วย key ได้แก่ ส่วนหัว X-Api-Key การสร้างและเพิกถอน key ในหน้าจัดการ การเทียบด้วยแฮช และโควตาคำขอแยกของแต่ละ key ไม่มี OAuth และนี่เป็นความตั้งใจ เพราะสถานการณ์การใช้งานของ API นี้อยู่ฝั่งเซิร์ฟเวอร์ทั้งหมด คือสร้างลิงก์ ดึงสถิติ รับคอนเวอร์ชัน และไม่มีอันใดที่มีบุคคลที่สามซึ่งผู้ใช้ต้องให้ความยินยอม
ถ้าวันหนึ่งต้องเปิดให้แอปของคนอื่นเข้าถึงบัญชีของลูกค้าเรา key จะไม่พออีกต่อไป ผู้ใช้ต้องมองเห็นว่าตนอนุญาตอะไรให้ใคร และต้องตัดแอปหนึ่งออกได้โดยไม่ทำให้ที่เหลือพัง ตรงจุดนั้นเองที่ OAuth เลิกเป็นพิธีการ และกลายเป็นทางเลือกเดียวที่ซื่อตรง
ตัวการเรียกเองอยู่ในเอกสาร API รวมถึงรหัสตอบกลับที่ได้เมื่อ key ไม่ถูกต้องและเมื่อ key ถูกเพิกถอนแล้ว ส่วนหัวข้อใกล้เคียง บทวิเคราะห์เรื่องทำไม redirect 301 จึงเปลี่ยน POST ให้เป็น GET คุ้มค่าแก่การอ่านสำหรับทุกคนที่กำลังวางระบบรับ postback เพราะคำขอที่ผ่านการยืนยันตัวตนแล้วอาจไปถึงปลายทางด้วยเมท็อดที่ต่างจากตอนส่งออก