phucbui2it
Java SoftwareEngineer
Trang chủVề tôi
HomeBlog[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

1 tháng 3, 2026•
#security
•20 views
[OAuth] Bài 1: Khai tử Implicit Grant & Kỷ nguyên bắt buộc của PKCE

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):

  1. Attacker cài một ứng dụng độc hại trên cùng thiết bị.

  2. Attacker chặn đường dẫn Callback/Redirect URI.

  3. Attacker đánh cắp được authorization_code trả về từ server.

  4. Vì server không yêu cầu client_secret (do là public client), Attacker dùng authorization_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 ra code_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_challenge và phương thức băm (S256). Server lưu code_challenge nà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_code và chuỗi code_verifier gốc (chưa băm).

  • Bước 5 (Xác thực): Server tự tay băm code_verifier vừa nhận bằng SHA-256. Nếu kết quả khớp hoàn toàn với code_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ệ.

DANH MỤC

SERIES

TỪ KHÓA

MỚI NHẤT

Built by PhucBui2. The source code is available on GitHub.

security7Database5database4backend3performance-tuning1spring1java1indexing1System Design1Backend1
The Database Internals5Mastering Modern OAuth3Modern Identity Architecture1

Series: Mastering Modern OAuth

[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?
Part 1

[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?

[OAuth] Bài 1: Khai tử Implicit Grant & Kỷ nguyên bắt buộc của PKCE
Part 2

[OAuth] Bài 1: Khai tử Implicit Grant & Kỷ nguyên bắt buộc của PKCE

Đang đọc

[OAuth] Bài 2: Khóa chặt Access Token với DPoP và Refresh Token Rotation
Part 3

[OAuth] Bài 2: Khóa chặt Access Token với DPoP và Refresh Token Rotation

Database5Security4
01

[OAuth] Bài 2: Khóa chặt Access Token với DPoP và Refresh Token Rotation

1/3/2026
02

[OAuth] Bài 1: Khai tử Implicit Grant & Kỷ nguyên bắt buộc của PKCE

1/3/2026
03

[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?

1/3/2026
04

[Series] Database Internals - Bài 2: Giải mã "Thùng sách" – Nghệ thuật sắp xếp Slotted Page Layout

24/2/2026
05

[Series] Database Internals - Bài 1: Tại sao RDBMS lưu dữ liệu khác với File thông thường?

23/2/2026