[OAuth] Bài 1: Khai tử Implicit Grant & Kỷ nguyên bắt buộc của PKCE
![[OAuth] Bài 1: Khai tử Implicit Grant & Kỷ nguyên bắt buộc của PKCE](/_next/image?url=https%3A%2F%2Fnllgiwbecqskpipvcvpt.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fblog-images%2F0.14058804452103146.png&w=3840&q=75)
Chào anh em.
Bài 0 đã dọn dẹp xong các khái niệm nền tảng. Ở bài này, chúng ta đi thẳng vào kỹ thuật phòng thủ cốt lõi đầu tiên mà RFC 9700 yêu cầu: PKCE (Proof Key for Code Exchange) và Exact URI Matching.
1. Lỗ hổng của luồng Authorization Code truyền thống
Luồng Authorization Code ban đầu được thiết kế cho các ứng dụng có backend (Confidential Client) - nơi có thể cất giấu client_secret an toàn.
Tuy nhiên, với Single Page Apps (React/Angular) hoặc Mobile Apps (Public Client), việc giấu client_secret là bất khả thi. Nếu không có client_secret, quy trình xin Token sẽ xuất hiện kẽ hở (Authorization Code Interception Attack):
Attacker cài một ứng dụng độc hại trên cùng thiết bị.
Attacker chặn đường dẫn Callback/Redirect URI.
Attacker đánh cắp được
authorization_codetrả về từ server.Vì server không yêu cầu
client_secret(do là public client), Attacker dùngauthorization_codeđổi lấy Access Token hợp lệ.
2. Giải pháp: Cách PKCE hoạt động
PKCE sinh ra để khóa chặt quá trình đổi Token, đảm bảo chỉ client ban đầu khởi tạo request mới có thể nhận được Token cuối cùng.
Cơ chế này dựa trên việc tạo ra một khóa dùng một lần cho mỗi phiên đăng nhập:
Bước 1 (Tạo khóa): Client sinh ra một chuỗi ngẫu nhiên gọi là
code_verifier. Sau đó băm chuỗi này bằng thuật toán SHA-256 để tạo racode_challenge.Bước 2 (Gửi yêu cầu): Client gọi lên Authorization Server để xin đăng nhập, đính kèm
code_challengevà phương thức băm (S256). Server lưucode_challengenày lại.Bước 3 (Nhận Code): Người dùng đăng nhập thành công. Server trả về
authorization_code.Bước 4 (Đổi Token): Client gửi request đổi Token. Lúc này, bắt buộc phải đính kèm
authorization_codevà chuỗicode_verifiergốc (chưa băm).Bước 5 (Xác thực): Server tự tay băm
code_verifiervừa nhận bằng SHA-256. Nếu kết quả khớp hoàn toàn vớicode_challengeđã lưu ở Bước 2, Token mới được cấp.
Kết quả: Dù Attacker có trộm được authorization_code ở Bước 3, tiến trình vẫn thất bại vì Attacker không nắm giữ code_verifier gốc.
3. RFC 9700 thay đổi luật chơi như thế nào?
Trước đây, PKCE chỉ được khuyên dùng cho Public Clients. Nhưng hiện tại, RFC 9700 bắt buộc sử dụng PKCE cho TẤT CẢ các client, bao gồm cả Confidential Clients (các backend service).
Lý do: Phòng thủ chiều sâu (Defense in Depth). Việc dùng PKCE kết hợp với client_secret ở backend sẽ ngăn chặn triệt để các rủi ro về Code Injection và CSRF (Cross-Site Request Forgery), ngay cả khi log hệ thống vô tình làm lộ authorization_code.
Với các framework hiện đại, việc tích hợp cấu hình này rất đơn giản. Ví dụ trong file application.yml của một ứng dụng backend, cấu hình cho một public client bắt buộc dùng PKCE chỉ mất vài dòng:
YAML
spring:
security:
oauth2:
client:
registration:
my-app:
client-id: "my-public-client"
client-authentication-method: "none" # Không dùng secret
authorization-grant-type: "authorization_code"
scope: "openid, profile"
(Các framework sinh ra sau này đa phần đã tự động xử lý việc sinh verifier và challenge ngầm bên dưới).
4. Chốt chặn cuối: Exact URI Matching (Khớp chính xác URI)
Bên cạnh PKCE, RFC 9700 đóng nốt một lỗ hổng phổ biến ở khâu Redirect: Tuyệt đối không sử dụng Wildcard (*).
Nhiều hệ thống ngày xưa cấu hình Redirect URI dạng https://*.mycompany.com/callback để tiện cho việc deploy lên nhiều môi trường. Hacker có thể lợi dụng điều này, setup một sub-domain độc hại (vd: https://hacker.mycompany.com/callback) để lừa Authorization Server trả code về đó.
Luật mới: Authorization Server phải thực hiện Exact String Matching (so khớp chính xác từng ký tự) cho Redirect URI. Cấu hình đường dẫn nào, trả về đúng đường dẫn đó, không có ngoại lệ.
![[OAuth] Bài 0: Vì sao những kiến thức bảo mật bạn biết có thể đã lỗi thời?](/_next/image?url=https%3A%2F%2Fnllgiwbecqskpipvcvpt.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fblog-images%2F0.9385837373059448.webp&w=3840&q=75)
![[OAuth] Bài 2: Khóa chặt Access Token với DPoP và Refresh Token Rotation](/_next/image?url=https%3A%2F%2Fnllgiwbecqskpipvcvpt.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fblog-images%2F0.33546581234837536.png&w=3840&q=75)