
ในโลกของการพัฒนาแอปพลิเคชันยุคใหม่ ทั้งบนเว็บ, โมบายล์ หรือแม้แต่ API ต่างๆ การรักษาความปลอดภัยและการยืนยันตัวตนของผู้ใช้งาน (Authentication) รวมถึงการกำหนดสิทธิ์การเข้าถึงข้อมูล (Authorization) ถือเป็นหัวใจสำคัญที่ไม่สามารถมองข้ามได้เลยครับ ความผิดพลาดเพียงเล็กน้อยอาจนำไปสู่ความเสี่ยงด้านความปลอดภัยครั้งใหญ่ ทำให้ข้อมูลผู้ใช้รั่วไหล หรือระบบถูกโจมตีได้ง่ายๆ
บทความนี้ SiamLancard.com ขอพาคุณดำดิ่งสู่โลกของสองเทคโนโลยีหลักที่เข้ามาปฏิวัติวิธีการจัดการด้านความปลอดภัยเหล่านี้ นั่นคือ OAuth 2.0 และ JWT (JSON Web Token) Authentication ครับ เราจะมาทำความเข้าใจว่าแต่ละตัวคืออะไร ทำงานอย่างไร มีความสำคัญอย่างไร และที่สำคัญที่สุดคือ พวกมันทำงานร่วมกันได้อย่างไร เพื่อสร้างระบบยืนยันตัวตนและการอนุญาตที่แข็งแกร่ง ยืดหยุ่น และมีประสิทธิภาพ พร้อมทั้งเจาะลึกในทุกรายละเอียดที่คุณจำเป็นต้องรู้ ตั้งแต่พื้นฐานไปจนถึงแนวปฏิบัติที่ดีที่สุด เพื่อให้คุณสามารถนำไปประยุกต์ใช้กับโปรเจกต์ของคุณได้อย่างมั่นใจและปลอดภัยที่สุดครับ!
สารบัญ
- ทำความเข้าใจโลกของ Authentication และ Authorization
- เจาะลึก OAuth 2.0: มาตรฐานแห่งการมอบอำนาจ
- ไขความลับ JWT (JSON Web Token): กุญแจสู่การยืนยันตัวตนแบบไร้สถานะ
- ผสานพลัง OAuth 2.0 และ JWT Authentication
- ตัวอย่าง Code Snippet: การสร้างและตรวจสอบ JWT
- ตารางเปรียบเทียบ: Session-based vs. Token-based (JWT) Authentication
- แนวปฏิบัติที่ดีที่สุดและข้อควรระวังด้านความปลอดภัย
- กรณีศึกษาและการนำไปใช้จริง
- คำถามที่พบบ่อย (FAQ)
- สรุปและ Call-to-Action
ทำความเข้าใจโลกของ Authentication และ Authorization
ก่อนที่เราจะลงลึกไปถึง OAuth 2.0 และ JWT สิ่งสำคัญคือต้องเข้าใจความแตกต่างพื้นฐานระหว่างคำสองคำที่มักจะถูกใช้สลับกันอยู่บ่อยครั้ง นั่นคือ Authentication (การยืนยันตัวตน) และ Authorization (การอนุญาต) ครับ
Authentication (การยืนยันตัวตน)
คือกระบวนการพิสูจน์ว่าบุคคลหรือเอนทิตีที่กำลังพยายามเข้าถึงระบบหรือทรัพยากรนั้น คือใคร จริงๆ ยกตัวอย่างเช่น เมื่อคุณเข้าสู่ระบบอีเมลของคุณโดยการกรอกชื่อผู้ใช้และรหัสผ่าน ระบบจะทำการตรวจสอบว่าข้อมูลที่คุณให้มานั้นตรงกับข้อมูลที่บันทึกไว้หรือไม่ หากตรงกัน ก็เท่ากับว่าคุณได้รับการยืนยันตัวตนแล้วว่าเป็นเจ้าของบัญชีนั้นครับ
- ตัวอย่าง: การเข้าสู่ระบบด้วยชื่อผู้ใช้และรหัสผ่าน, การใช้ลายนิ้วมือ, การสแกนใบหน้า, การยืนยัน OTP
Authorization (การอนุญาต)
คือกระบวนการกำหนดว่าบุคคลหรือเอนทิตีที่ได้รับการยืนยันตัวตนแล้วนั้น มีสิทธิ์ทำอะไรได้บ้าง หรือเข้าถึงทรัพยากรใดได้บ้าง ยกตัวอย่างเช่น หลังจากที่คุณเข้าสู่ระบบอีเมลได้แล้ว ระบบจะตรวจสอบต่อว่าคุณมีสิทธิ์ในการอ่านอีเมล, ส่งอีเมล, หรือลบอีเมลได้หรือไม่ หากคุณเป็นผู้ดูแลระบบ คุณอาจมีสิทธิ์ในการสร้างบัญชีใหม่หรือแก้ไขการตั้งค่าระบบได้ด้วย ซึ่งนี่คือกระบวนการ Authorization ครับ
- ตัวอย่าง: ผู้ใช้ทั่วไปสามารถดูข้อมูลโปรไฟล์ได้ แต่ไม่สามารถแก้ไขข้อมูลของผู้อื่นได้, แอดมินสามารถสร้างและลบบัญชีผู้ใช้ได้
กล่าวโดยสรุปคือ Authentication ตอบคำถาม “คุณเป็นใคร?” ส่วน Authorization ตอบคำถาม “คุณทำอะไรได้บ้าง?” ครับ
ในอดีต การจัดการ Authentication และ Authorization มักจะอยู่ในรูปแบบของ Session-based Authentication ที่เซิร์ฟเวอร์จะสร้าง Session ID ให้กับผู้ใช้หลังจากการเข้าสู่ระบบสำเร็จ และเก็บสถานะของผู้ใช้ไว้บนเซิร์ฟเวอร์ (Stateful) ซึ่งมีข้อจำกัดด้าน Scalability และความยืดหยุ่นในการใช้งานกับแอปพลิเคชันที่ซับซ้อนและกระจายตัวในปัจจุบัน นี่จึงเป็นจุดเริ่มต้นที่ทำให้ OAuth 2.0 และ JWT เข้ามามีบทบาทสำคัญครับ
เจาะลึก OAuth 2.0: มาตรฐานแห่งการมอบอำนาจ
OAuth 2.0 คืออะไร?
OAuth 2.0 (Open Authorization 2.0) คือเฟรมเวิร์กมาตรฐาน (Framework) สำหรับการอนุญาต (Authorization) ที่ช่วยให้แอปพลิเคชันหนึ่ง (Client) สามารถเข้าถึงข้อมูลที่ถูกป้องกันบนเซิร์ฟเวอร์อื่น (Resource Server) ในนามของผู้ใช้งาน (Resource Owner) ได้ โดยที่ผู้ใช้งานไม่จำเป็นต้องเปิดเผยชื่อผู้ใช้และรหัสผ่านโดยตรงให้กับแอปพลิเคชันนั้นๆ ครับ
สิ่งสำคัญที่ต้องทำความเข้าใจคือ OAuth 2.0 ไม่ใช่ Protocol สำหรับ Authentication โดยตรง แต่เป็น Protocol สำหรับ Authorization นั่นหมายความว่ามันไม่ได้บอกว่า “ใครคือคุณ” (Authentication) แต่มันบอกว่า “แอปพลิเคชันนี้มีสิทธิ์ทำอะไรในนามของคุณบ้าง” (Authorization) ครับ
“OAuth 2.0 is an authorization framework that enables an application to obtain limited access to a user’s protected resources without exposing the user’s credentials to the application.”
มันถูกพัฒนาขึ้นเพื่อแก้ไขปัญหาด้านความปลอดภัยและความซับซ้อนของ OAuth 1.0 และกลายเป็นมาตรฐานอุตสาหกรรมที่ใช้กันอย่างแพร่หลายในปัจจุบัน ไม่ว่าจะเป็นการเข้าสู่ระบบด้วยบัญชี Google, Facebook, LINE หรือการให้แอปพลิเคชันต่างๆ เข้าถึงข้อมูลของคุณบนแพลตฟอร์มอื่นครับ
ทำไมต้องใช้ OAuth 2.0?
การนำ OAuth 2.0 มาใช้นั้นมีประโยชน์มากมายครับ:
- ความปลอดภัยที่เหนือกว่า: ผู้ใช้ไม่จำเป็นต้องแชร์รหัสผ่านให้กับแอปพลิเคชันที่สามโดยตรง ทำให้ลดความเสี่ยงที่รหัสผ่านจะถูกขโมยหรือนำไปใช้ในทางที่ผิด
- การควบคุมที่ละเอียดยิ่งขึ้น: ผู้ใช้สามารถกำหนดสิทธิ์ (Scope) ได้อย่างละเอียดว่าต้องการให้แอปพลิเคชันเข้าถึงข้อมูลส่วนใดได้บ้าง และสามารถเพิกถอนสิทธิ์ได้ทุกเมื่อ
- ประสบการณ์ผู้ใช้ที่ดีขึ้น: ทำให้ผู้ใช้สามารถเข้าถึงบริการต่างๆ ได้อย่างราบรื่นและสะดวกสบาย โดยไม่ต้องสร้างบัญชีใหม่หรือจดจำรหัสผ่านจำนวนมาก
- รองรับแพลตฟอร์มที่หลากหลาย: ออกแบบมาเพื่อรองรับการใช้งานกับแอปพลิเคชันหลากหลายประเภท ไม่ว่าจะเป็น Web Application, Mobile Application, Desktop Application หรือแม้แต่ Server-to-Server communication
- ลดภาระในการจัดการรหัสผ่าน: นักพัฒนาแอปพลิเคชันไม่ต้องรับผิดชอบในการเก็บรหัสผ่านของผู้ใช้ ทำให้ลดความซับซ้อนและภาระด้านความปลอดภัยลงได้
ศัพท์สำคัญใน OAuth 2.0 (The OAuth 2.0 Actors)
ในการทำความเข้าใจ OAuth 2.0 เราต้องรู้จักกับ “ผู้เล่น” หรือ “บทบาท” หลักๆ 4 อย่างที่ทำงานร่วมกันครับ
- Resource Owner (ผู้ใช้งาน): คือบุคคลหรือเอนทิตีที่เป็นเจ้าของข้อมูลที่ถูกป้องกัน เช่น ตัวคุณเองที่ต้องการให้แอปพลิเคชันเข้าถึงรูปภาพใน Google Photos ของคุณ
- Client (แอปพลิเคชัน): คือแอปพลิเคชันที่ต้องการเข้าถึงข้อมูลของผู้ใช้งาน เช่น แอปพลิเคชันแต่งรูปที่ต้องการเข้าถึง Google Photos ของคุณเพื่อดึงรูปภาพมาแต่ง
- Authorization Server (เซิร์ฟเวอร์ผู้อนุญาต): คือเซิร์ฟเวอร์ที่ทำหน้าที่ยืนยันตัวตนผู้ใช้งานและออก Access Token ให้กับ Client เมื่อผู้ใช้งานได้ให้สิทธิ์แล้ว เช่น Google’s Authorization Server
- Resource Server (เซิร์ฟเวอร์ที่เก็บข้อมูล): คือเซิร์ฟเวอร์ที่เก็บข้อมูลที่ถูกป้องกันและสามารถให้ Client เข้าถึงข้อมูลนั้นได้เมื่อ Client แสดง Access Token ที่ถูกต้อง เช่น Google Photos API
ประเภทของ Grant Type (Flows) ใน OAuth 2.0
OAuth 2.0 มีกลไกหลายรูปแบบที่เรียกว่า “Grant Types” ซึ่งเป็นวิธีการที่ Client จะได้รับ Access Token แต่ละ Grant Type ถูกออกแบบมาสำหรับสถานการณ์และประเภทของ Client ที่แตกต่างกัน โดยมีระดับความปลอดภัยและความซับซ้อนที่แตกต่างกันไปครับ
1. Authorization Code Grant (และ PKCE)
นี่คือ Grant Type ที่แนะนำและปลอดภัยที่สุดสำหรับ Client ที่สามารถเก็บ Client Secret ได้อย่างปลอดภัย (เช่น Web Application ที่ทำงานบนฝั่ง Server-side) และสำหรับ Public Client (เช่น Single Page Application (SPA) และ Mobile Application) ที่ใช้ร่วมกับ PKCE (Proof Key for Code Exchange) ครับ
ขั้นตอนการทำงานโดยละเอียด:
- ผู้ใช้งาน (Resource Owner) ต้องการเข้าสู่ระบบหรือใช้แอปพลิเคชัน (Client): ผู้ใช้งานคลิกปุ่ม “เข้าสู่ระบบด้วย Google” หรือ “เชื่อมต่อกับ Facebook” ในแอปพลิเคชัน
- Client ส่ง Request ไปยัง Authorization Server: แอปพลิเคชันจะ Redirect ผู้ใช้งานไปยังหน้า Login ของ Authorization Server (เช่น Google Login Page) พร้อมส่งพารามิเตอร์สำคัญไปดังนี้:
response_type=code: บอกว่าต้องการ Authorization Codeclient_id: รหัสประจำตัวของแอปพลิเคชันredirect_uri: URL ที่ Authorization Server จะส่ง Authorization Code กลับมาscope: สิทธิ์ที่ต้องการ (เช่นemail profile)state: ค่าที่สร้างขึ้นแบบสุ่มเพื่อป้องกัน CSRF (Client จะตรวจสอบค่านี้เมื่อได้รับ Response กลับมา)- PKCE (สำหรับ Public Client): เพิ่ม
code_challengeและcode_challenge_methodเข้าไปด้วย
- ผู้ใช้งานยืนยันตัวตนและให้สิทธิ์ (Authentication & Authorization): ผู้ใช้งาน Login ที่ Authorization Server และยินยอมให้สิทธิ์ (Authorize) แก่แอปพลิเคชันตาม Scope ที่ร้องขอ
- Authorization Server ส่ง Authorization Code กลับไปที่ Client: หลังจากผู้ใช้งานให้สิทธิ์แล้ว Authorization Server จะ Redirect ผู้ใช้งานกลับมายัง
redirect_uriของแอปพลิเคชัน พร้อมแนบauthorization_code(และstate) มาด้วย - Client แลก Authorization Code เป็น Access Token (และ Refresh Token): แอปพลิเคชันจะส่ง Request แบบ Server-to-Server ไปยัง Token Endpoint ของ Authorization Server โดยส่ง
authorization_codeที่ได้รับ,client_id,client_secret(สำหรับ Confidential Client),redirect_uriและgrant_type=authorization_codeไปด้วย- PKCE (สำหรับ Public Client): เพิ่ม
code_verifierเข้าไปด้วย เพื่อพิสูจน์ว่า Client ที่แลก Token เป็น Client เดียวกันกับที่ร้องขอ Authorization Code
- PKCE (สำหรับ Public Client): เพิ่ม
- Authorization Server ตรวจสอบและออก Token: Authorization Server จะตรวจสอบความถูกต้องของข้อมูลทั้งหมด และหากถูกต้อง ก็จะออก
Access Token(มักจะเป็น JWT),Refresh Token(ถ้ามี),expires_in(อายุของ Access Token) และtoken_type(มักจะเป็น Bearer) ให้กับแอปพลิเคชัน - Client ใช้ Access Token เพื่อเข้าถึง Resource Server: แอปพลิเคชันจะใช้ Access Token ที่ได้รับ เพื่อเรียกใช้ API หรือเข้าถึงข้อมูลที่ถูกป้องกันบน Resource Server
ประโยชน์: ปลอดภัยที่สุด เพราะ Authorization Code ถูกส่งผ่าน Redirect URL ใน Browser เพียงครั้งเดียว และการแลก Token เกิดขึ้นแบบ Server-to-Server ทำให้ Client Secret ไม่ถูกเปิดเผยต่อ Browser โดยตรง
Use Case: Web Application, Single Page Application (SPA) และ Mobile Application (โดยเฉพาะอย่างยิ่งเมื่อใช้ร่วมกับ PKCE)
2. Client Credentials Grant
Grant Type นี้ใช้เมื่อแอปพลิเคชัน (Client) ต้องการเข้าถึงทรัพยากรของตัวเอง ไม่ใช่ในนามของผู้ใช้งาน โดยตรง เช่น การเรียกใช้ API จาก Server-to-Server หรือ Machine-to-Machine ครับ
ขั้นตอนการทำงาน:
- Client ส่ง Request ไปยัง Token Endpoint: แอปพลิเคชันจะส่ง Request โดยตรงไปยัง Token Endpoint ของ Authorization Server พร้อมกับ
grant_type=client_credentials,client_idและclient_secret(มักจะส่งในรูปแบบ Basic Authentication Header) - Authorization Server ตรวจสอบและออก Access Token: Authorization Server จะตรวจสอบ
client_idและclient_secretและหากถูกต้อง ก็จะออกAccess Tokenให้กับแอปพลิเคชัน - Client ใช้ Access Token เพื่อเข้าถึง Resource Server: แอปพลิเคชันใช้ Access Token เพื่อเรียกใช้ API ที่ต้องการ
ประโยชน์: เหมาะสำหรับระบบที่ไม่มีผู้ใช้งานมาเกี่ยวข้องโดยตรง หรือสำหรับการสื่อสารระหว่าง Microservices
Use Case: Server-to-Server communication, Microservices, Batch processing
3. Implicit Grant (DEPRECATED)
Implicit Grant ถูกออกแบบมาสำหรับ Public Client ที่ไม่มี Server-side (เช่น Single Page Application รุ่นเก่าๆ) โดยที่ Access Token จะถูกส่งกลับมาโดยตรงใน URL fragment หลังจากการ Redirect ของ Authorization Server ครับ
ขั้นตอนการทำงานโดยสรุป:
- ผู้ใช้งานถูก Redirect ไปยัง Authorization Server
- ผู้ใช้งาน Login และให้สิทธิ์
- Authorization Server Redirect กลับมายัง Client พร้อมแนบ
access_tokenมาใน URL fragment ทันที
ข้อเสียและเหตุผลที่ไม่ควรใช้แล้ว: เนื่องจาก Access Token ถูกส่งกลับมาใน URL fragment ทำให้มีความเสี่ยงที่จะถูกดักจับหรือรั่วไหลผ่าน Browser History, Referrer Headers หรือ Log Files ได้ง่าย จึงไม่แนะนำให้ใช้แล้ว และถูกแทนที่ด้วย Authorization Code Grant + PKCE ครับ
Use Case: ไม่ควรใช้แล้ว
4. Resource Owner Password Credentials Grant (DEPRECATED/Limited Use)
Grant Type นี้อนุญาตให้ Client ร้องขอ Access Token ได้โดยตรงจาก Authorization Server โดยการส่งชื่อผู้ใช้และรหัสผ่านของผู้ใช้งานไปด้วยครับ
ขั้นตอนการทำงานโดยสรุป:
- Client รับชื่อผู้ใช้และรหัสผ่านจากผู้ใช้งาน
- Client ส่งชื่อผู้ใช้, รหัสผ่าน,
client_id,client_secretและgrant_type=passwordไปยัง Token Endpoint - Authorization Server ตรวจสอบและออก Access Token
ข้อเสียและเหตุผลที่ไม่แนะนำ: Client จะต้องเก็บชื่อผู้ใช้และรหัสผ่านของผู้ใช้งาน ซึ่งขัดกับหลักการพื้นฐานของ OAuth 2.0 ที่ต้องการให้ผู้ใช้งานไม่เปิดเผยรหัสผ่านกับ Client โดยตรง Grant Type นี้ควรใช้เฉพาะในกรณีที่ Client เป็นแอปพลิเคชันที่น่าเชื่อถืออย่างยิ่ง (เช่น แอปพลิเคชันขององค์กรเอง) และไม่มีทางเลือกอื่นที่เหมาะสมกว่าครับ
Use Case: Legacy systems, Highly trusted first-party applications (แต่ก็ยังไม่แนะนำ)
Scope: การจำกัดสิทธิ์การเข้าถึง
Scope ใน OAuth 2.0 คือกลไกที่ใช้ในการกำหนดขอบเขตของสิทธิ์ที่ Client จะได้รับอนุญาตให้เข้าถึงข้อมูลของผู้ใช้งานได้ครับ มันเป็นสตริงที่ระบุประเภทของทรัพยากรหรือการกระทำที่ Client ต้องการ เช่น:
read_profile: อนุญาตให้อ่านข้อมูลโปรไฟล์write_posts: อนุญาตให้โพสต์ข้อความemail: อนุญาตให้เข้าถึงที่อยู่อีเมล
ผู้ใช้งานจะเป็นผู้พิจารณาและอนุมัติ Scope ที่ Client ร้องขอ ทำให้ผู้ใช้งานควบคุมข้อมูลของตนเองได้มากขึ้นครับ การกำหนด Scope ที่เหมาะสมและจำกัดเท่าที่จำเป็น (Principle of Least Privilege) เป็นสิ่งสำคัญสำหรับความปลอดภัย
Access Token และ Refresh Token
เมื่อกระบวนการ OAuth 2.0 สำเร็จ Authorization Server จะออก Token ให้กับ Client หลักๆ มีสองประเภทครับ
Access Token
- คือ Credentials ที่ Client ใช้เพื่อเข้าถึง Protected Resources บน Resource Server
- มีอายุสั้น (เช่น 15 นาทีถึง 1 ชั่วโมง) เพื่อจำกัดความเสียหายหากถูกขโมย
- มักจะเป็น JWT (JSON Web Token) ซึ่งเราจะอธิบายในส่วนถัดไป
- Client จะส่ง Access Token ไปกับทุกๆ Request ที่ต้องการเข้าถึง Resource Server โดยปกติจะอยู่ใน HTTP Header:
Authorization: Bearer [Access Token]
Refresh Token
- คือ Credentials ที่ Client ใช้เพื่อขอ Access Token ใหม่ เมื่อ Access Token เดิมหมดอายุ
- มีอายุที่ยาวนานกว่า Access Token (เช่น หลายวัน หลายเดือน หรือแม้แต่ไม่หมดอายุจนกว่าจะถูกเพิกถอน)
- ถูกเก็บรักษาอย่างปลอดภัย และมักจะใช้ในรูปแบบ Server-to-Server เท่านั้น เพื่อป้องกันการรั่วไหล
- ถ้า Refresh Token ถูกขโมย ผู้โจมตีสามารถใช้มันเพื่อขอ Access Token ใหม่ได้เรื่อยๆ ดังนั้นการจัดการ Refresh Token จึงเป็นสิ่งสำคัญมากครับ
กลไก Access Token (อายุสั้น) คู่กับ Refresh Token (อายุยาว) เป็นแนวทางปฏิบัติที่ดีเยี่ยมในการเพิ่มความปลอดภัยครับ หาก Access Token ถูกขโมย ผู้โจมตีจะมีเวลาจำกัดในการใช้งาน ในขณะเดียวกัน ผู้ใช้งานก็ไม่จำเป็นต้อง Login ใหม่บ่อยๆ เมื่อ Access Token หมดอายุ Client สามารถใช้ Refresh Token เพื่อขอ Access Token ใหม่ได้อย่างราบรื่นครับ
หากต้องการเจาะลึกเกี่ยวกับความแตกต่างระหว่าง Authentication และ Authorization Server เพิ่มเติม สามารถ อ่านเพิ่มเติมได้ที่นี่ ครับ
ไขความลับ JWT (JSON Web Token): กุญแจสู่การยืนยันตัวตนแบบไร้สถานะ
JWT คืออะไร?
JWT (JSON Web Token) คือมาตรฐานเปิด (RFC 7519) ที่ใช้วิธีการในการนำเสนอข้อมูล (Claims) ระหว่างสองฝ่ายได้อย่างปลอดภัยและกระชับ โดยข้อมูลเหล่านี้จะถูกเข้ารหัสและลงลายเซ็นดิจิทัล เพื่อยืนยันความถูกต้องและป้องกันการแก้ไขครับ
หัวใจสำคัญของ JWT คือการเป็น Stateless และ Self-contained:
- Stateless: เซิร์ฟเวอร์ไม่จำเป็นต้องเก็บข้อมูล Session ของผู้ใช้แต่ละคนไว้ ทำให้ระบบมีความยืดหยุ่นและสามารถขยายขนาดได้ง่าย (Scalability)
- Self-contained: JWT เก็บข้อมูลที่จำเป็นทั้งหมดเกี่ยวกับผู้ใช้งาน (เช่น ID, บทบาท, สิทธิ์) ไว้ในตัวมันเอง ทำให้ Resource Server สามารถตรวจสอบข้อมูลได้ทันทีโดยไม่ต้องไปสอบถามจากฐานข้อมูลหรือ Authorization Server อีกครั้ง
โครงสร้างของ JWT: Header, Payload, Signature
JWT ประกอบด้วยสามส่วนหลักๆ ที่ถูกคั่นด้วยจุด (.) และเข้ารหัสด้วย Base64Url-encoded ได้แก่ Header, Payload และ Signature ครับ
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
เรามาดูแต่ละส่วนกันครับ:
1. Header (ส่วนหัว)
Header เป็นอ็อบเจกต์ JSON ที่ระบุประเภทของ Token (typ) ซึ่งมักจะเป็น “JWT” และระบุอัลกอริทึมที่ใช้ในการเข้ารหัสลายเซ็น (alg) เช่น HS256 (HMAC SHA256) หรือ RS256 (RSA SHA256) ครับ
{
"alg": "HS256",
"typ": "JWT"
}
ส่วนนี้จะถูก Base64Url-encode เพื่อสร้างส่วนแรกของ JWT
2. Payload (ส่วนเนื้อหา/Claims)
Payload คืออ็อบเจกต์ JSON ที่เก็บข้อมูลที่เราต้องการจะส่งหรือ “Claims” ครับ Claims คือข้อมูลเกี่ยวกับเอนทิตี (โดยทั่วไปคือผู้ใช้งาน) และข้อมูลเพิ่มเติม Metadata โดยแบ่งออกเป็นสามประเภทหลักๆ:
- Registered Claims: เป็น Claims ที่ถูกกำหนดไว้ล่วงหน้าโดยมาตรฐาน JWT แต่ไม่บังคับต้องใช้ เช่น:
iss(Issuer): ผู้ออก Tokensub(Subject): เจ้าของ Token (เช่น User ID)aud(Audience): ผู้รับ Token ที่ถูกต้องexp(Expiration Time): เวลาหมดอายุของ Token (ในรูปแบบ Unix timestamp)nbf(Not Before): เวลาที่ Token จะเริ่มใช้งานได้iat(Issued At): เวลาที่ Token ถูกออกjti(JWT ID): รหัสเฉพาะของ JWT
- Public Claims: เป็น Claims ที่สามารถกำหนดเองได้ แต่ควรระบุชื่อตาม IANA JSON Web Token Registry หรือใช้ URI เพื่อป้องกันการชนกันของชื่อ
- Private Claims: เป็น Claims ที่กำหนดขึ้นมาเองตามความต้องการของแอปพลิเคชัน โดยตกลงกันระหว่างผู้ออกและผู้รับ Token โดยไม่จำเป็นต้องลงทะเบียนใดๆ เช่น
"role": "admin"หรือ"company": "SiamLancard"
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"role": "user"
}
ส่วนนี้จะถูก Base64Url-encode เพื่อสร้างส่วนที่สองของ JWT
3. Signature (ส่วนลายเซ็น)
Signature ใช้เพื่อตรวจสอบว่า JWT ไม่ได้ถูกแก้ไขระหว่างทาง และยืนยันว่าผู้ออก Token เป็นผู้ที่ถูกต้องครับ การสร้าง Signature ทำโดยการนำ Base64Url-encoded ของ Header, Base64Url-encoded ของ Payload, และ Secret Key (หรือ Private Key ในกรณีของ RS256) มาเข้ารหัสด้วยอัลกอริทึมที่ระบุใน Header ครับ
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
ผลลัพธ์ที่ได้คือ Signature ที่ถูก Base64Url-encode เพื่อสร้างส่วนที่สามของ JWT
เมื่อทั้งสามส่วนถูกสร้างขึ้น ก็จะนำมาต่อกันด้วยจุด (.) กลายเป็น JWT ที่สมบูรณ์แบบครับ
ประเภทของ JWT: JWS และ JWE
โดยทั่วไปเมื่อพูดถึง JWT เรามักจะหมายถึง JWS (JSON Web Signature) ซึ่งเป็น JWT ที่มีลายเซ็นเพื่อยืนยันความถูกต้องและป้องกันการแก้ไข แต่ไม่ได้เข้ารหัสเนื้อหาในส่วน Payload ทำให้ข้อมูลใน Payload สามารถอ่านได้ (แต่ไม่สามารถแก้ไขได้โดยไม่ถูกตรวจจับ) ครับ
นอกจากนี้ยังมี JWE (JSON Web Encryption) ซึ่งเป็น JWT อีกรูปแบบหนึ่งที่นอกจากจะมีลายเซ็นแล้ว ยังมีการเข้ารหัสเนื้อหาในส่วน Payload ด้วย ทำให้ข้อมูลที่อยู่ใน Payload ไม่สามารถอ่านได้โดยผู้ที่ไม่มี Key สำหรับถอดรหัส JWE เหมาะสำหรับกรณีที่ต้องการส่งข้อมูลที่อ่อนไหวมากๆ ครับ แต่มีความซับซ้อนในการใช้งานมากกว่า JWS
ทำไมต้องใช้ JWT?
JWT มีข้อดีหลายประการที่ทำให้เป็นที่นิยมในการใช้งาน:
- Statelessness: ลดภาระของเซิร์ฟเวอร์ในการจัดการ Session ทำให้ระบบ Scalable ได้ดีขึ้น โดยเฉพาะใน Microservices Architecture
- Scalability: เนื่องจากไม่มี Session State บนเซิร์ฟเวอร์ ทำให้ง่ายต่อการเพิ่มจำนวนเซิร์ฟเวอร์ (Horizontal Scaling)
- Cross-domain Friendly: สามารถใช้งานได้ง่ายกับการสื่อสารข้ามโดเมน หรือระหว่าง Service ต่างๆ โดยไม่ต้องกังวลเรื่อง CORS หรือ Session Management
- Compact & Efficient: มีขนาดเล็กและสามารถส่งผ่าน URL, POST parameter หรือ HTTP header ได้อย่างรวดเร็ว
- Decentralized Authentication: Resource Server สามารถตรวจสอบ Token ได้ด้วยตัวเองโดยไม่ต้องสอบถาม Authorization Server ทุกครั้ง (หลังจากตรวจสอบ Secret Key หรือ Public Key)
ข้อควรระวังในการใช้ JWT
แม้จะมีข้อดีมากมาย แต่ JWT ก็มีข้อควรระวังในการใช้งานเช่นกันครับ:
- ไม่ควรเก็บข้อมูล Sensitive มากๆ ใน Payload: เนื่องจากข้อมูลใน Payload สามารถถูกอ่านได้ (ถึงแม้จะถูก Base64Url-encode) จึงไม่ควรเก็บข้อมูลที่อ่อนไหวเป็นพิเศษ เช่น รหัสผ่าน หรือข้อมูลบัตรเครดิต หากจำเป็นต้องเก็บ ควรใช้ JWE แทน
- การจัดการ Revocation: JWT โดยธรรมชาติแล้วเป็น Stateless ทำให้การเพิกถอน (Revocation) Token ที่ถูกออกไปแล้วทำได้ยาก (เช่น ผู้ใช้งานต้องการ Logout หรือ Token ถูกขโมย) วิธีแก้ปัญหานี้มักจะต้องใช้กลไกเพิ่มเติม เช่น Blacklisting (เก็บ Token ที่ถูกเพิกถอนไว้ในฐานข้อมูล) ซึ่งจะลดความเป็น Stateless ลงบางส่วน
- การป้องกัน XSS/CSRF: การเก็บ JWT ใน Local Storage มีความเสี่ยงต่อการถูกโจมตีแบบ XSS (Cross-Site Scripting) ได้ หากแฮกเกอร์สามารถรัน Script บนหน้าเว็บของคุณได้ พวกเขาก็จะสามารถเข้าถึง JWT และนำไปใช้ได้ การเก็บ JWT ใน HttpOnly Cookie จะช่วยป้องกัน XSS ได้ แต่ก็ยังมีความเสี่ยงต่อ CSRF (Cross-Site Request Forgery) หากไม่มีการป้องกันที่เหมาะสม
- Secret Key: Secret Key ที่ใช้ในการสร้าง Signature ต้องเป็นความลับสูงสุด และมีความแข็งแกร่งเพียงพอ หาก Key นี้รั่วไหล ผู้โจมตีสามารถสร้าง JWT ปลอมขึ้นมาได้
ผสานพลัง OAuth 2.0 และ JWT Authentication
พวกเขาทำงานร่วมกันอย่างไร?
ดังที่เราได้กล่าวไปแล้วว่า OAuth 2.0 เป็นเฟรมเวิร์กสำหรับการอนุญาต (Authorization) ในขณะที่ JWT เป็นรูปแบบหนึ่งของ Token ที่สามารถใช้เป็น Access Token หรือ ID Token ได้ การรวมกันของทั้งสองจึงเป็นการทำงานที่เสริมซึ่งกันและกันได้อย่างลงตัวครับ
โดยทั่วไปแล้ว ในกระบวนการของ OAuth 2.0 เมื่อ Client ได้รับการอนุญาตจากผู้ใช้งานแล้ว Authorization Server จะออก Access Token ให้กับ Client ซึ่ง Access Token นี้เองที่มักจะอยู่ในรูปแบบของ JWT ครับ
- OAuth 2.0 จัดการกระบวนการทั้งหมดตั้งแต่การร้องขอสิทธิ์, การยืนยันตัวตนผู้ใช้งานบน Authorization Server, การให้สิทธิ์โดยผู้ใช้งาน ไปจนถึงการออก Token (Access Token และ Refresh Token)
- JWT เป็น “ภาชนะ” หรือ “รูปแบบ” ที่ใช้บรรจุข้อมูล (Claims) เกี่ยวกับสิทธิ์การเข้าถึงของผู้ใช้งานและข้อมูลอื่นๆ ภายใน Access Token นั้น เมื่อ Client ได้รับ JWT (Access Token) มาแล้ว จะใช้มันเพื่อเรียกใช้ Protected Resources บน Resource Server
เมื่อ Resource Server ได้รับ Request พร้อมกับ Access Token (ซึ่งเป็น JWT) มันจะทำการตรวจสอบความถูกต้องของ JWT นั้นโดยใช้ Secret Key หรือ Public Key ที่ได้ตกลงไว้กับ Authorization Server หาก Signature ถูกต้องและ Token ยังไม่หมดอายุ Resource Server ก็จะสามารถอ่าน Claims ภายใน JWT เพื่อตัดสินใจว่าผู้ใช้งานมีสิทธิ์เข้าถึงทรัพยากรนั้นหรือไม่ โดยไม่จำเป็นต้องติดต่อ Authorization Server ซ้ำอีกครั้ง (ยกเว้นกรณีที่ต้องการข้อมูลผู้ใช้แบบ Real-time หรือเพิกถอน Token)
OpenID Connect (OIDC) และ JWT: Authentication Layer บน OAuth 2.0
แม้ว่า OAuth 2.0 จะไม่ใช่ Protocol สำหรับ Authentication โดยตรง แต่ก็มีส่วนขยายที่เรียกว่า OpenID Connect (OIDC) ที่สร้างขึ้นมาบน OAuth 2.0 เพื่อเพิ่มความสามารถในการยืนยันตัวตน (Authentication) เข้ามาครับ
- OIDC ใช้ OAuth 2.0 เป็น Framework พื้นฐาน: OIDC ใช้ Flow ของ OAuth 2.0 (โดยเฉพาะ Authorization Code Grant) เพื่อให้ Client ได้รับ Access Token
- ID Token (ที่เป็น JWT): นอกจาก Access Token แล้ว OIDC ยังมีการออก ID Token ซึ่งเป็น JWT อีกตัวหนึ่งครับ ID Token มีวัตถุประสงค์หลักเพื่อ “ยืนยันตัวตน” ของผู้ใช้งานให้กับ Client โดยตรง
- Claims ใน ID Token: ID Token จะมี Claims มาตรฐานเกี่ยวกับผู้ใช้งาน เช่น
sub(Subject ID),name,email,picture,genderฯลฯ ซึ่ง Client สามารถอ่านและนำไปใช้เพื่อสร้าง User Session ของตัวเองได้
ดังนั้น เมื่อคุณเห็นปุ่ม “Login with Google” หรือ “Login with Facebook” นั่นคือการทำงานของ OpenID Connect บนพื้นฐานของ OAuth 2.0 ที่ใช้ JWT เป็นทั้ง Access Token (สำหรับ Authorization) และ ID Token (สำหรับ Authentication) ครับ OIDC ทำให้การทำ Single Sign-On (SSO) ข้ามแอปพลิเคชันและบริการต่างๆ เป็นไปได้อย่างราบรื่นและปลอดภัย
สนใจเกี่ยวกับ OpenID Connect และการประยุกต์ใช้ในการทำ SSO สามารถ ศึกษาเพิ่มเติมได้ที่นี่ ครับ
Workflow ตัวอย่าง: การใช้ OAuth 2.0 (Authorization Code Grant) กับ JWT
เรามาดูภาพรวมของ Workflow การทำงานร่วมกันระหว่าง OAuth 2.0 (Authorization Code Grant) และ JWT/OIDC ในสถานการณ์จริงกันครับ
- ผู้ใช้งาน (Resource Owner) ต้องการเข้าสู่ระบบแอปพลิเคชัน (Client): ผู้ใช้งานคลิกปุ่ม “Login with Google” บนแอปพลิเคชัน
- Client Redirect ไปยัง Authorization Server: แอปพลิเคชันสร้าง Authorization Request และ Redirect ผู้ใช้งานไปยัง Google’s Authorization Server พร้อมพารามิเตอร์เช่น
client_id,redirect_uri,scope(เช่นopenid email profile) และresponse_type=code(+ PKCE) - ผู้ใช้งานยืนยันตัวตนและให้สิทธิ์: ผู้ใช้งาน Login ด้วยบัญชี Google และยินยอมให้แอปพลิเคชันเข้าถึงข้อมูล (ตาม Scope ที่ร้องขอ)
- Authorization Server ส่ง Authorization Code กลับ Client: Google’s Authorization Server Redirect ผู้ใช้งานกลับมายัง
redirect_uriของแอปพลิเคชัน พร้อมแนบauthorization_codeมาด้วย - Client แลก Code เป็น Token: แอปพลิเคชัน (บนฝั่ง Server-side) ส่ง
authorization_code,client_id,client_secret(และcode_verifierสำหรับ PKCE) ไปยัง Token Endpoint ของ Google’s Authorization Server - Authorization Server ออก Token (JWT): Google’s Authorization Server ตรวจสอบข้อมูลและออก Token กลับมาให้แอปพลิเคชัน ซึ่งประกอบด้วย:
- Access Token (JWT): สำหรับใช้เข้าถึง Google APIs (Resource Server)
- ID Token (JWT): สำหรับยืนยันตัวตนผู้ใช้งานกับแอปพลิเคชัน
- Refresh Token: สำหรับขอ Access Token ใหม่เมื่อหมดอายุ
- Client ใช้ ID Token (JWT) เพื่อยืนยันตัวตนผู้ใช้งาน: แอปพลิเคชันตรวจสอบ Signature ของ ID Token เพื่อยืนยันว่า Token ไม่ถูกแก้ไข และอ่าน Claims ใน Payload เพื่อทราบข้อมูลผู้ใช้งาน (เช่น User ID, email, name) จากนั้นสร้าง Session ของผู้ใช้งานในแอปพลิเคชันเอง
- Client ใช้ Access Token (JWT) เพื่อเรียกใช้ Resource Server: เมื่อผู้ใช้งานต้องการให้แอปพลิเคชันเข้าถึงข้อมูลบน Google (เช่น ดึงรายชื่อติดต่อ), แอปพลิเคชันจะส่ง Request ไปยัง Google Contacts API (Resource Server) พร้อมแนบ Access Token (JWT) ใน
Authorization: BearerHeader - Resource Server ตรวจสอบ Access Token (JWT): Google Contacts API ตรวจสอบ Signature ของ Access Token (JWT) และอ่าน Claims เพื่อยืนยันว่า Token ถูกต้องและผู้ใช้งานมีสิทธิ์เข้าถึงข้อมูลที่ร้องขอ จากนั้นจึงส่งข้อมูลกลับไปยังแอปพลิเคชัน
นี่คือภาพรวมของการทำงานที่ซับซ้อนแต่มีประสิทธิภาพ ซึ่งเป็นพื้นฐานของระบบความปลอดภัยสมัยใหม่จำนวนมากครับ
ตัวอย่าง Code Snippet: การสร้างและตรวจสอบ JWT
เพื่อให้เห็นภาพการทำงานของ JWT ชัดเจนยิ่งขึ้น เรามาดูตัวอย่าง Code Snippet ในการสร้าง (Sign) และตรวจสอบ (Verify) JWT ด้วยภาษาโปรแกรมยอดนิยมอย่าง Python และ Node.js กันครับ
สร้างและตรวจสอบ JWT ด้วย Python
เราจะใช้ไลบรารี PyJWT ครับ ติดตั้งได้ด้วย pip install PyJWT
import jwt
import datetime
import time
# Secret Key ที่ใช้ในการลงนาม (Signature) - ต้องเก็บเป็นความลับสุดยอด!
SECRET_KEY = "your-very-secret-key-that-no-one-should-know"
# 1. สร้าง JWT (Encoding)
def create_jwt_token(user_id: str, role: str, expires_in_minutes: int = 30):
payload = {
"sub": user_id, # Subject: ID ของผู้ใช้งาน
"role": role, # Custom Claim: บทบาทของผู้ใช้งาน
"iat": datetime.datetime.utcnow(), # Issued At: เวลาที่ Token ถูกออก
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=expires_in_minutes) # Expiration Time: เวลาหมดอายุ
}
# เข้ารหัส JWT โดยใช้อัลกอริทึม HS256
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return token
# 2. ตรวจสอบ JWT (Decoding)
def verify_jwt_token(token: str):
try:
# ถอดรหัส JWT และตรวจสอบลายเซ็น, เวลาหมดอายุ
decoded_payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return decoded_payload
except jwt.ExpiredSignatureError:
print("Token หมดอายุแล้ว")
return None
except jwt.InvalidTokenError as e:
print(f"Token ไม่ถูกต้อง: {e}")
return None
# --- ตัวอย่างการใช้งาน ---
if __name__ == "__main__":
user_id = "user123"
user_role = "admin"
# สร้าง Access Token สำหรับผู้ดูแลระบบ มีอายุ 1 นาที
access_token = create_jwt_token(user_id, user_role, expires_in_minutes=1)
print(f"สร้าง Access Token สำเร็จ: {access_token}")
# ตรวจสอบ Access Token ทันที
print("\n--- ตรวจสอบ Token ทันที ---")
decoded_data = verify_jwt_token(access_token)
if decoded_data:
print(f"Token ถูกต้อง! ผู้ใช้: {decoded_data['sub']}, บทบาท: {decoded_data['role']}")
# ลองรอให้ Token หมดอายุ (1 นาที)
print("\n--- รอ 65 วินาที เพื่อให้ Token หมดอายุ ---")
time.sleep(65)
# ตรวจสอบ Access Token อีกครั้งหลังหมดอายุ
print("\n--- ตรวจสอบ Token หลังหมดอายุ ---")
decoded_data_expired = verify_jwt_token(access_token)
if not decoded_data_expired:
print("ไม่สามารถตรวจสอบ Token ที่หมดอายุได้ (ตามที่คาดไว้)")
# สร้าง Token ที่มีข้อมูลต่างไป และลองถอดรหัสด้วย Secret Key เดิม
print("\n--- ทดสอบ Token ปลอม (Payload ถูกแก้ไข) ---")
# สมมติว่ามีผู้ไม่หวังดีแก้ไข Payload โดยตรง
# ในความเป็นจริง Token ที่ถูกแก้ไข Payload จะมี Signature ที่ไม่ตรงกัน
# แต่ถ้าเราสร้าง Token ปลอมขึ้นมาใหม่ด้วย Secret Key ที่ต่างกัน
# PyJWT จะปฏิเสธ Token นั้นทันที
# ตัวอย่าง Token ที่ถูกสร้างจาก SECRET_KEY ที่ต่างไป (สมมติว่ารั่วไหล)
FAKE_SECRET_KEY = "another-secret-key"
fake_token = jwt.encode({"sub": "hacker", "role": "admin"}, FAKE_SECRET_KEY, algorithm="HS256")
print(f"สร้าง Token ปลอมสำเร็จ: {fake_token}")
# ลองตรวจสอบ Token ปลอมด้วย SECRET_KEY จริง
print("\n--- ตรวจสอบ Token ปลอมด้วย Secret Key จริง ---")
decoded_fake_data = verify_jwt_token(fake_token)
if not decoded_fake_data:
print("ไม่สามารถตรวจสอบ Token ปลอมได้ (ตามที่คาดไว้)")
สร้างและตรวจสอบ JWT ด้วย Node.js
เราจะใช้ไลบรารี jsonwebtoken ครับ ติดตั้งได้ด้วย npm install jsonwebtoken
const jwt = require('jsonwebtoken');
// Secret Key ที่ใช้ในการลงนาม (Signature) - ต้องเก็บเป็นความลับสุดยอด!
const SECRET_KEY = "your-very-secret-key-that-no-one-should-know";
// 1. สร้าง JWT (Signing)
function createJwtToken(userId, role, expiresIn = '30m') {
const payload = {
sub: userId, // Subject: ID ของผู้ใช้งาน
role: role // Custom Claim: บทบาทของผู้ใช้งาน
};
// ลงนาม JWT โดยใช้อัลกอริทึม HS256 และกำหนดเวลาหมดอายุ
const token = jwt.sign(payload, SECRET_KEY, { algorithm: 'HS256', expiresIn: expiresIn });
return token;
}
// 2. ตรวจสอบ JWT (Verifying)
function verifyJwtToken(token) {
try {
// ตรวจสอบ JWT และตรวจสอบลายเซ็น, เวลาหมดอายุ
const decodedPayload = jwt.verify(token, SECRET_KEY, { algorithms: ['HS256'] });
return decodedPayload;
} catch (error) {
if (error.name === 'TokenExpiredError') {
console.error("Token หมดอายุแล้ว");
} else if (error.name === 'JsonWebTokenError') {
console.error(`Token ไม่ถูกต้อง: ${error.message}`);
} else {
console.error(`เกิดข้อผิดพลาดในการตรวจสอบ Token: ${error.message}`);
}
return null;
}
}
// --- ตัวอย่างการใช้งาน ---
async function runExamples() {
const userId = "user123";
const userRole = "viewer";
// สร้าง Access Token สำหรับผู้ใช้ทั่วไป มีอายุ 1 นาที
const accessToken = createJwtToken(userId, userRole, '1m');
console.log(`สร้าง Access Token สำเร็จ: ${accessToken}`);
// ตรวจสอบ Access Token ทันที
console.log("\n--- ตรวจสอบ Token ทันที ---");
const decodedData = verifyJwtToken(accessToken);
if (decodedData) {
console.log(`Token ถูกต้อง! ผู้ใช้: ${decodedData.sub}, บทบาท: ${decodedData.role}`);
}
// ลองรอให้ Token หมดอายุ (1 นาที + นิดหน่อย)
console.log("\n--- รอ 65 วินาที เพื่อให้ Token หมดอายุ ---");
await new Promise(resolve => setTimeout(resolve, 65 * 1000));
// ตรวจสอบ Access Token อีกครั้งหลังหมดอายุ
console.log("\n--- ตรวจสอบ Token หลังหมดอายุ ---");
const decodedDataExpired = verifyJwtToken(accessToken);
if (!decodedDataExpired) {
console.log("ไม่สามารถตรวจสอบ Token ที่หมดอายุได้ (ตามที่คาดไว้)");
}
// สร้าง Token ที่มีข้อมูลต่างไป และลองถอดรหัสด้วย Secret Key เดิม
console.log("\n--- ทดสอบ Token ปลอม (Payload ถูกแก้ไข) ---");
// ตัวอย่าง Token ที่ถูกสร้างจาก SECRET_KEY ที่ต่างไป (สมมติว่ารั่วไหล)
const FAKE_SECRET_KEY = "another-secret-key";
const fakeToken = jwt.sign({ sub: "hacker", role: "admin" }, FAKE_SECRET_KEY, { algorithm: 'HS256', expiresIn: '10m' });
console.log(`สร้าง Token ปลอมสำเร็จ: ${fakeToken}`);
// ลองตรวจสอบ Token ปลอมด้วย SECRET_KEY จริง
console.log("\n--- ตรวจสอบ Token ปลอมด้วย Secret Key จริง ---");
const decodedFakeData = verifyJwtToken(fakeToken);
if (!decodedFakeData) {
console.log("ไม่สามารถตรวจสอบ Token ปลอมได้ (ตามที่คาดไว้)");
}
}
runExamples();
การส่ง JWT ใน HTTP Request Header
เมื่อ Client ได้รับ Access Token (ที่เป็น JWT) มาแล้ว การใช้งานเพื่อเรียกใช้ Protected API บน Resource Server จะทำได้โดยการส่ง Access Token นั้นไปใน HTTP Header ที่ชื่อว่า Authorization ในรูปแบบ Bearer Token ครับ
GET /api/v1/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Resource Server จะรับ Request นี้มา และทำการดึงค่า Token ออกจาก Header เพื่อนำไปตรวจสอบความถูกต้อง (Verify) ตามขั้นตอนที่เราได้กล่าวไปในส่วนของการตรวจสอบ JWT ครับ
ตารางเปรียบเทียบ: Session-based vs. Token-based (JWT) Authentication
เพื่อช่วยให้คุณเห็นภาพรวมและตัดสินใจเลือกเทคโนโลยีที่เหมาะสมกับโปรเจกต์ของคุณ เรามาดูตารางเปรียบเทียบระหว่างระบบยืนยันตัวตนแบบ Session-based (แบบดั้งเดิม) กับ Token-based ที่ใช้ JWT กันครับ
| คุณสมบัติ | Session-based Authentication | Token-based Authentication (JWT) |
|---|---|---|
| สถานะ (State) | Stateful (เซิร์ฟเวอร์ต้องเก็บ Session ID และข้อมูล Session ของผู้ใช้) | Stateless (Token เก็บข้อมูลทั้งหมดในตัวมันเอง เซิร์ฟเวอร์ไม่ต้องเก็บสถานะ) |
| Scalability | ท้าทายในการทำ Horizontal Scaling เพราะต้องแชร์ Session State ระหว่างเซิร์ฟเวอร์ หรือใช้ Sticky Sessions | ง่ายต่อการทำ Horizontal Scaling เพราะแต่ละ Request สามารถตรวจสอบ Token ได้เองโดยไม่ต้องพึ่งพาเซิร์ฟเวอร์อื่น |
| Cross-domain / CORS | ท้าทายในการจัดการ Cookie ข้ามโดเมน ต้องตั้งค่า CORS อย่างระมัดระวังและใช้ Credentials | ง่ายต่อการใช้งานข้ามโดเมน เพราะ Token ถูกส่งใน Header ไม่ใช่ Cookie |
| Mobile / API Friendly | ไม่เหมาะเท่าไหร่ เพราะ Mobile App มักไม่รองรับ Cookie และการจัดการ Session ที่ซับซ้อน | เหมาะอย่างยิ่งสำหรับ Mobile App และ RESTful APIs เนื่องจาก Token เป็น Self-contained และส่งง่าย |
| การเพิกถอน (Revocation) | ง่าย: สามารถลบ Session ออกจากเซิร์ฟเวอร์ได้ทันทีเมื่อผู้ใช้ Logout หรือเมื่อเกิดเหตุการณ์ผิดปกติ | ท้าทาย: Token เป็น Stateless การเพิกถอนต้องใช้กลไกเพิ่มเติม เช่น Blacklisting หรือ Short-lived token + Refresh token |
| ความปลอดภัย (ทั่วไป) | เสี่ยงต่อ CSRF (หากไม่มีการป้องกัน) และ Session Hijacking (หาก Cookie ไม่ปลอดภัย) | เสี่ยงต่อ XSS (หากเก็บใน Local Storage) และการรั่วไหลของ Secret Key |
| การจัดการ Credentials | ต้องจัดการ Cookie, Session ID และการตรวจสอบ Session บนเซิร์ฟเวอร์ | ต้องจัดการ Secret Key (หรือ Public/Private Key), การเข้ารหัสและถอดรหัส Token |
| Payload Data | เซิร์ฟเวอร์เก็บข้อมูลผู้ใช้แยกต่างหาก | ข้อมูลผู้ใช้ (Claims) ถูกเก็บในตัว Token สามารถอ่านได้ (แต่ไม่สามารถแก้ไขได้โดยไม่ถูกตรวจจับ) |
| Use Case | Web Application แบบดั้งเดิม, ระบบที่มีสถานะคงที่ | Web Application (SPA), Mobile Application, RESTful APIs, Microservices, Single Sign-On (SSO) |
จากตารางจะเห็นได้ว่า JWT มีข้อได้เปรียบที่โดดเด่นในเรื่องของ Scalability และความยืดหยุ่นในการใช้งานกับแพลตฟอร์มสมัยใหม่ อย่างไรก็ตาม การจัดการด้านความปลอดภัยและการเพิกถอน Token ก็ต้องได้รับการพิจารณาอย่างรอบคอบเช่นกันครับ
แนวปฏิบัติที่ดีที่สุดและข้อควรระวังด้านความปลอดภัย
การนำ OAuth 2.0 และ JWT มาใช้นั้นจำเป็นต้องคำนึงถึงแนวทางปฏิบัติที่ดีที่สุดและข้อควรระวังด้านความปลอดภัยอย่างจริงจัง เพื่อป้องกันการโจมตีและรักษาข้อมูลของผู้ใช้งานให้ปลอดภัยครับ
สำหรับ OAuth 2.0
- เลือก Grant Type ที่เหมาะสม:
- สำหรับ Web Application และ Public Client (SPA, Mobile) ควรใช้ Authorization Code Grant ร่วมกับ PKCE เสมอ เพื่อความปลอดภัยสูงสุด
- หลีกเลี่ยง Implicit Grant และ Resource Owner Password Credentials Grant เว้นแต่ในสถานการณ์ที่จำกัดและมีความจำเป็นอย่างยิ่ง
- ใช้ HTTPS เสมอ: การสื่อสารทั้งหมดระหว่าง Client, Authorization Server และ Resource Server จะต้องอยู่ภายใต้ HTTPS (SSL/TLS) เพื่อป้องกันการดักจับข้อมูล (Man-in-the-Middle Attack)
- Validate Redirect URI: Authorization Server ควรตรวจสอบ
redirect_uriที่ Client ส่งมาอย่างเคร่งครัด โดยเปรียบเทียบกับ URI ที่ลงทะเบียนไว้ล่วงหน้า เพื่อป้องกันการโจมตีแบบ Open Redirect - เก็บ Client Secret ให้ปลอดภัย: สำหรับ Confidential Client (เช่น Web Server-side Application)
client_secretต้องถูกเก็บไว้บนเซิร์ฟเวอร์อย่างปลอดภัย และไม่ควรเปิดเผยต่อ Public - จำกัด Scope ให้แคบที่สุด (Least Privilege): Client ควรร้องขอสิทธิ์ (Scope) เท่าที่จำเป็นจริงๆ เท่านั้น ไม่ควรร้องขอสิทธิ์ทั้งหมดโดยไม่จำเป็น
- จัดการ Access Token และ Refresh Token อย่างมีประสิทธิภาพ:
- Access Token ควรมีอายุสั้น
- Refresh Token ควรมีอายุยาวกว่าและถูกเก็บรักษาอย่างปลอดภัยบน Server-side เท่านั้น ควรถูกใช้เพื่อขอ Access Token ใหม่เท่านั้น และควรมีการเพิกถอนได้ (Revocation)
- ใช้
stateParameter: ใช้stateParameter ใน Authorization Request เพื่อป้องกันการโจมตีแบบ Cross-Site Request Forgery (CSRF)
สำหรับ JWT
- ใช้ Secret Key ที่แข็งแกร่งและไม่ซ้ำใคร: Secret Key ที่ใช้ในการสร้าง Signature ต้องมีความยาวเพียงพอ (อย่างน้อย 32-64 อักขระ) สุ่ม และไม่ซ้ำกับ Key อื่นๆ
- อย่าเก็บข้อมูล Sensitive มากๆ ใน Payload: ข้อมูลใน Payload ของ JWS สามารถถูกอ่านได้ (แม้จะถูก Base64Url-encode) ดังนั้นไม่ควรเก็บข้อมูลที่อ่อนไหวเป็นพิเศษ หากจำเป็นต้องเก็บ ควรใช้ JWE แทน
- ตรวจสอบ Signature เสมอ: ทุกครั้งที่ Resource Server หรือ Client ได้รับ JWT จะต้องตรวจสอบ Signature เพื่อยืนยันว่า Token ไม่ถูกแก้ไขและมาจากผู้ออกที่ถูกต้อง
- ใช้
exp(expiration) claim อย่างเคร่งครัด: กำหนดเวลาหมดอายุของ JWT และตรวจสอบอย่างสม่ำเสมอ เพื่อจำกัดระยะเวลาที่ Token สามารถถูกใช้งานได้หากถูกขโมย - จัดการ Revocation สำหรับ Access Token ที่ถูกขโมย: แม้ JWT จะเป็น Stateless แต่ในบางกรณี (เช่น ผู้ใช้ Logout, เปลี่ยนรหัสผ่าน, Token ถูกขโมย) การเพิกถอน Token ทันทีก็เป็นสิ่งจำเป็น สามารถทำได้โดยใช้ Blacklisting (เก็บ ID ของ Token ที่ถูกเพิกถอนไว้)
- การเก็บ JWT ใน Client:
- HttpOnly Cookie: เป็นวิธีที่แนะนำสำหรับการป้องกัน XSS โดย Token จะถูกส่งไปกับทุก Request โดยอัตโนมัติ และ JavaScript ไม่สามารถเข้าถึงได้ แต่ยังคงเสี่ยงต่อ CSRF หากไม่มีการป้องกันเพิ่มเติม (เช่น SameSite cookie, CSRF token)
- Local Storage/Session Storage: ง่ายต่อการใช้งานด้วย JavaScript แต่มีความเสี่ยงสูงต่อ XSS หากแฮกเกอร์รัน Script บนหน้าเว็บของคุณได้ พวกเขาสามารถเข้าถึง Token ได้ทันที
- ป้องกัน Replay Attacks: สำหรับ Token ที่มีอายุสั้น การใช้
jti(JWT ID) ใน Payload และเก็บไว้ใน Blacklist ชั่วคราวหลังจากใช้งานแล้วครั้งหนึ่ง สามารถช่วยป้องกันการนำ Token เดิมมาใช้ซ้ำได้ (แม้จะขัดกับ Stateless บ้าง) - พิจารณาใช้ JWE สำหรับข้อมูลที่ต้องการการเข้ารหัส: หากมีความจำเป็นต้องส่งข้อมูลที่อ่อนไหวใน Token จริงๆ JWE คือทางเลือกที่เหมาะสมในการเข้ารหัส Payload
กรณีศึกษาและการนำไปใช้จริง
OAuth 2.0 และ JWT ได้กลายเป็นรากฐานของระบบความปลอดภัยในแอปพลิเคชันและบริการต่างๆ ทั่วโลกครับ นี่คือตัวอย่างกรณีศึกษาและการนำไปใช้จริงที่พบเห็นได้บ่อย:
- Social Logins (Google, Facebook, LINE Login): นี่คือตัวอย่างคลาสสิกของ OAuth 2.0 (และ OpenID Connect) ครับ เมื่อคุณเลือก “เข้าสู่ระบบด้วย Google” บนเว็บไซต์ใดๆ คุณกำลังใช้ OAuth 2.0 เพื่ออนุญาตให้เว็บไซต์นั้นเข้าถึงข้อมูลโปรไฟล์พื้นฐานของคุณ โดยไม่ต้องให้รหัสผ่าน Google โดยตรง และ ID Token (JWT) จาก Google จะยืนยันตัวตนของคุณกับเว็บไซต์นั้น
- API Authentication: บริษัทส่วนใหญ่ที่ให้บริการ API (เช่น Stripe, Twilio, GitHub API) ใช้ OAuth 2.0 เพื่ออนุญาตให้แอปพลิเคชันของนักพัฒนาเข้าถึงข้อมูลหรือฟังก์ชันของ API ในนามของผู้ใช้งาน หรือใช้ Client Credentials Grant สำหรับ Server-to-Server API calls
- Single Sign-On (SSO): ในองค์กรขนาดใหญ่ หรือระบบที่มีแอปพลิเคชันย่อยหลายตัว OIDC ที่ใช้ JWT เป็น ID Token ช่วยให้ผู้ใช้งานสามารถ Login เพียงครั้งเดียวและเข้าถึงแอปพลิเคชันทั้งหมดได้โดยอัตโนมัติ
- Microservices Architecture: ในสถาปัตยกรรม Microservices ที่ประกอบด้วยบริการขนาดเล็กจำนวนมาก JWT เป็นตัวเลือกที่ยอดเยี่ยมสำหรับการ Authentication และ Authorization เนื่องจากความเป็น Stateless และ Self-contained ทำให้แต่ละ Microservice สามารถตรวจสอบ Token ได้ด้วยตัวเองโดยไม่ต้องพึ่งพา Centralized Session Store
- Mobile Application Backend: Mobile App มักใช้ JWT เพื่อจัดการ Authentication กับ Backend API เนื่องจากใช้งานง่าย ไม่ต้องพึ่งพา Cookie และสามารถจัดการ Access Token และ Refresh Token ได้อย่างยืดหยุ่น
คำถามที่พบบ่อย (FAQ)
OAuth 2.0 กับ OpenID Connect ต่างกันอย่างไร?
OAuth 2.0 เป็น Authorization Framework ที่เน้นการมอบสิทธิ์การเข้าถึงทรัพยากร ไม่ใช่การยืนยันตัวตนโดยตรงครับ ส่วน OpenID Connect (OIDC) เป็น Authentication Layer ที่สร้างขึ้นบน OAuth 2.0 เพื่อเพิ่มความสามารถในการยืนยันตัวตน (Authentication) ผู้ใช้งาน โดยจะออก ID Token (ซึ่งเป็น JWT) ที่มีข้อมูลผู้ใช้งานให้กับ Client เพื่อยืนยันตัวตนครับ
ควรเก็บ Access Token (JWT) ไว้ที่ไหนใน Frontend? (Local Storage vs. HttpOnly Cookie)
นี่เป็นคำถามที่ถกเถียงกันมานานครับ:
- Local Storage: เข้าถึงง่ายด้วย JavaScript แต่มีความเสี่ยงสูงต่อการโจมตีแบบ XSS หากมีช่องโหว่บนเว็บไซต์ แฮกเกอร์สามารถขโมย JWT ไปใช้ได้ทันที
- HttpOnly Cookie: ป้องกัน XSS ได้ดีกว่า เพราะ JavaScript ไม่สามารถเข้าถึง Cookie ได้โดยตรง Token จะถูกส่งไปกับทุก Request โดยอัตโนมัติ แต่ยังคงมีความเสี่ยงต่อ CSRF หากไม่มีการป้องกันเพิ่มเติม (เช่น SameSite cookie, CSRF token, หรือ CSRF token ใน Header)
โดยทั่วไปแล้ว HttpOnly Cookie (ร่วมกับการป้องกัน CSRF) มักถูกแนะนำมากกว่าสำหรับ Web Application เพื่อความปลอดภัยที่ดีที่สุดครับ สำหรับ Mobile App มักจะเก็บใน Secure Storage ที่จัดเตรียมโดยระบบปฏิบัติการ
JWT ปลอดภัยจริงหรือ? มีช่องโหว่อะไรบ้าง?
JWT ปลอดภัยจริงครับ