Trong series: Bảo mật
  1. 1 Ransomware là gì? Mã độc mã hóa tống tiền và cách phòng chống
  2. 2 SQL Injection là gì? Tấn công cơ sở dữ liệu và cách phòng chống
  3. 3 Tường lửa là gì? Vai trò và chức năng trong bảo mật mạng
  4. 4 MFA Là Gì? So Sánh MFA vs 2FA và Các Phương Thức Xác Thực
  5. 5 VPN là gì? Mạng riêng ảo, WireGuard và OpenVPN
  6. 6 Zero Trust Là Gì? Mô Hình Bảo Mật 'Không Tin Tưởng Ai'
  7. 7 Trojan là gì? Những thông tin cơ bản về mã độc Trojan
  8. 8 OAuth 2.0 là gì? Ủy quyền truy cập và đăng nhập bằng Google/Facebook
  9. 9 DNS Sinkhole là gì? Ứng dụng và cách dùng kỹ thuật DNS Sinkhole
  10. 10 Phishing là gì? Nhận diện và phòng chống tấn công lừa đảo trực tuyến
  11. 11 DDos là gì? Dấu hiệu, xử lý và cách phòng chống hiệu quả
  12. 12 Mã độc là gì? Phân loại, đặc tính và cách phòng tránh
✦ Tóm tắt nhanh
OAuth 2.0 là giao thức ủy quyền (authorization) cho phép ứng dụng truy cập tài nguyên của người dùng mà không cần biết mật khẩu. Tìm hiểu Authorization Code Flow, 4 roles, scope, và cách Google/GitHub...
Bài này thế nào?

OAuth 2.0 là giao thức ủy quyền mở (RFC 6749) cho phép ứng dụng bên thứ ba truy cập tài nguyên của người dùng trên một dịch vụ khác mà không cần biết mật khẩu. Đây là nền tảng của tính năng "Đăng nhập bằng Google", "Đăng nhập bằng Facebook" mà hàng tỷ người dùng hàng ngày.

Trong bài viết này, chúng ta sẽ tìm hiểu OAuth 2.0 hoạt động ra sao, phân biệt ủy quyền và xác thực, đi qua từng bước của Authorization Code Flow, và hiểu tại sao PKCE lại cần thiết cho ứng dụng SPA và mobile. Sau bài này, bạn sẽ biết chính xác điều gì xảy ra bên dưới mỗi lần bạn nhấn nút "Đăng nhập bằng Google".

OAuth 2.0 là gì? Ủy quyền vs Xác thực

Trước hết, cần phân biệt hai khái niệm thường bị nhầm lẫn: xác thực (authentication)ủy quyền (authorization).

  • Xác thực (Authentication) trả lời câu hỏi "Bạn là ai?" — tức là xác minh danh tính. Khi bạn nhập mật khẩu, hệ thống xác thực rằng bạn là chủ tài khoản.
  • Ủy quyền (Authorization) trả lời câu hỏi "Bạn được phép làm gì?" — tức là phân quyền truy cập sau khi danh tính đã được xác minh.

OAuth 2.0 là giao thức ủy quyền, không phải xác thực. Nó giải quyết bài toán: làm thế nào để một ứng dụng (ví dụ: Canva) có thể truy cập Google Drive của bạn mà không cần bạn tiết lộ mật khẩu Google?

Hãy hình dung analogy thực tế: khi bạn gửi xe tại một nhà hàng sang trọng, nhân viên valet được trao một chìa khóa valet (valet key) — chìa khóa đặc biệt chỉ cho phép nổ máy và đỗ xe, không mở được hộc đựng đồ, không cho phép đổ xăng tốc hành. Đó chính xác là cách OAuth 2.0 hoạt động: thay vì trao toàn bộ quyền kiểm soát tài khoản Google, bạn cấp cho Canva một "valet key" kỹ thuật số — access token — chỉ có quyền đọc file Drive, không đọc được Gmail hay thay đổi mật khẩu.

Tiêu chuẩn OAuth 2.0 được định nghĩa trong RFC 6749 (phát hành tháng 10/2012) bởi IETF. Đây là phiên bản thay thế hoàn toàn OAuth 1.0, đơn giản hóa quy trình và mở rộng hỗ trợ cho nhiều loại client hơn.

Điểm cốt lõi của OAuth 2.0:

  • Ứng dụng nhận được access token có thời hạn, không phải mật khẩu.
  • Người dùng kiểm soát scope — danh sách quyền cụ thể được cấp.
  • Token có thể bị thu hồi bất kỳ lúc nào mà không cần đổi mật khẩu.
  • Hỗ trợ nhiều loại client: web app, mobile app, SPA, thiết bị IoT.

4 Roles trong OAuth 2.0

RFC 6749 định nghĩa bốn vai trò (roles) trong một luồng OAuth 2.0 hoàn chỉnh. Hiểu rõ bốn roles này giúp bạn nắm được ai làm gì trong toàn bộ quá trình.

1. Resource Owner (Chủ tài nguyên)

Resource Owner là người dùng cuối — người sở hữu tài nguyên và có quyền cấp phép truy cập. Trong ví dụ "Canva truy cập Google Drive", Resource Owner là bạn — người dùng có tài khoản Google và sở hữu các file Drive.

Resource Owner không nhất thiết là con người; trong trường hợp machine-to-machine (Client Credentials flow), Resource Owner và Client có thể là cùng một thực thể.

2. Client (Ứng dụng yêu cầu truy cập)

Client là ứng dụng muốn truy cập tài nguyên thay mặt Resource Owner. Trong ví dụ trên, Canva là Client. Client phải đăng ký trước với Authorization Server để nhận client_idclient_secret.

Có hai loại Client:

  • Confidential client: Web app chạy trên server, có thể giữ client_secret an toàn.
  • Public client: SPA (React, Vue), mobile app — không thể giữ secret an toàn vì code chạy trên thiết bị của user. Cần dùng PKCE.

3. Authorization Server (Máy chủ ủy quyền)

Authorization Server là dịch vụ cấp token sau khi xác minh danh tính Resource Owner và lấy sự đồng ý. Google, GitHub, Facebook, Microsoft đều vận hành Authorization Server riêng.

Authorization Server có hai endpoint chính:

  • Authorization Endpoint (/auth): nhận yêu cầu ủy quyền, hiển thị consent screen, trả về authorization code.
  • Token Endpoint (/token): nhận authorization code, xác thực Client, trả về access token và refresh token.

4. Resource Server (Máy chủ tài nguyên)

Resource Server là API lưu trữ dữ liệu mà Client muốn truy cập. Resource Server nhận access token trong request header, xác thực token, và trả về dữ liệu nếu token hợp lệ và có đủ scope.

Trong thực tế, Authorization Server và Resource Server thường được triển khai cùng nhau (ví dụ: accounts.google.com cấp token, www.googleapis.com là Resource Server), nhưng RFC 6749 tách biệt chúng về mặt lý thuyết.

Authorization Code Flow — Step by step

Authorization Code Flow là grant type phổ biến nhất, được khuyến nghị cho web app và mobile app. Đây là flow an toàn nhất vì access token không bao giờ đi qua trình duyệt.

1. User click "Login with Google" trên App
2. App redirect → https://accounts.google.com/o/oauth2/auth
       ?client_id=APP_CLIENT_ID
       &redirect_uri=https://myapp.com/callback
       &response_type=code
       &scope=openid email profile
       &state=RANDOM_CSRF_TOKEN

3. User login + consent → Google redirect về:
       https://myapp.com/callback?code=AUTH_CODE&state=TOKEN

4. App backend POST tới Google:
       https://oauth2.googleapis.com/token
       client_id, client_secret, code, redirect_uri, grant_type=authorization_code

5. Google trả về:
       { access_token, expires_in, refresh_token, id_token }

6. App gọi API:
       GET https://www.googleapis.com/oauth2/v3/userinfo
       Authorization: Bearer ACCESS_TOKEN

Phân tích từng bước:

Bước 1–2: Authorization Request App tạo URL redirect với các tham số:

  • client_id: định danh app đã đăng ký với Google.
  • redirect_uri: URL Google sẽ chuyển hướng về sau khi user đồng ý (phải khớp với URI đã đăng ký).
  • response_type=code: chỉ định Authorization Code Flow.
  • scope: danh sách quyền yêu cầu.
  • state: token ngẫu nhiên để ngăn CSRF — app phải xác minh lại khi nhận callback.

Bước 3: Authorization Code Sau khi user đăng nhập và đồng ý trên consent screen của Google, Google redirect về redirect_uri kèm code — authorization code tồn tại chỉ khoảng 10 giây và chỉ dùng được một lần. Đây là một đặc điểm bảo mật quan trọng: code ngắn hạn và dùng một lần để ngăn replay attack.

Bước 4: Token Exchange App backend (không phải browser!) gửi POST request đến Token Endpoint của Google, bao gồm client_secret. Việc trao đổi này xảy ra trực tiếp giữa server với server qua HTTPS, không đi qua browser — đây là lý do tại sao access token không bị lộ trong browser history hay logs.

Bước 5: Token Response Google trả về:

  • access_token: token dùng để gọi API, thường hết hạn sau 1 giờ.
  • expires_in: số giây đến khi token hết hạn.
  • refresh_token: token dài hạn để xin access token mới khi hết hạn (chỉ cấp nếu scope có offline_access).
  • id_token: JWT chứa thông tin người dùng (chỉ có khi dùng OIDC với scope openid).

Bước 6: API Call App dùng access token trong header Authorization: Bearer <token> để gọi Resource Server. Đây là cú pháp chuẩn Bearer Token theo RFC 6750.

Các Grant Types

OAuth 2.0 định nghĩa nhiều grant types cho các tình huống khác nhau. Mỗi grant type phù hợp với một loại client và use case cụ thể.

Authorization Code (khuyến nghị)

Như đã trình bày ở trên, đây là grant type an toàn nhất cho:

  • Web app (confidential client): dùng client_secret trên server.
  • SPA và mobile app (public client): dùng PKCE thay cho client_secret.

Client Credentials

Dùng cho machine-to-machine (M2M) — khi không có người dùng tham gia. Ví dụ: microservice A cần gọi API của microservice B.

POST /token
grant_type=client_credentials
client_id=SERVICE_A_ID
client_secret=SERVICE_A_SECRET
scope=read:orders write:inventory

Service A tự xác thực bằng client_id + client_secret và nhận access token trực tiếp, không cần consent screen.

Device Authorization Grant (Device Code)

Dùng cho thiết bị không có trình duyệt hoặc màn hình nhỏ: Smart TV, game console, IoT device, CLI tool. Thiết bị hiển thị một URL ngắn và mã xác minh; người dùng nhập URL đó trên điện thoại hoặc máy tính, đăng nhập và đồng ý; thiết bị polling Authorization Server cho đến khi nhận được token.

Implicit Flow (deprecated)

Implicit Flow từng được dùng cho SPA trước khi CORS được hỗ trợ rộng rãi. Flow này trả access token trực tiếp trong URL fragment, bỏ qua bước trao đổi code — tiện lợi nhưng kém an toàn (token lộ trong browser history, referrer headers). Implicit Flow hiện không còn được khuyến nghị; OAuth 2.1 (draft) loại bỏ hoàn toàn. Hãy dùng Authorization Code + PKCE cho SPA.

OAuth 2.0 vs OpenID Connect (OIDC)

Đây là một trong những điểm gây nhầm lẫn nhất về OAuth 2.0: khi bạn "Đăng nhập bằng Google", bạn không chỉ dùng OAuth 2.0 — bạn đang dùng OpenID Connect (OIDC).

OAuth 2.0 thuần giải quyết bài toán ủy quyền: cấp quyền cho app truy cập tài nguyên. Nó không cung cấp cơ chế chuẩn để xác định "người dùng này là ai". Access token trong OAuth 2.0 thuần là opaque string — Resource Server biết token hợp lệ, nhưng không biết token thuộc về ai nếu không có thêm thông tin.

OpenID Connect (OIDC) là lớp xác thực xây trên OAuth 2.0, được định nghĩa bởi OpenID Foundation. OIDC bổ sung:

  1. ID Token: JWT (JSON Web Token) trả về cùng access token, chứa sub (subject/user ID), name, email, picture, và các claims khác về người dùng. Client có thể đọc ID Token mà không cần gọi thêm API.

JWT là gì? JSON Web Token và xác thực API

  1. /userinfo Endpoint: Resource Server endpoint chuẩn — gọi với access token để lấy thông tin người dùng dạng JSON.

  2. openid Scope: Scope đặc biệt kích hoạt OIDC. Khi request có scope=openid, Authorization Server trả về ID Token.

  3. Discovery Document: Endpoint /.well-known/openid-configuration trả về JSON mô tả tất cả endpoint, supported scopes, signing keys — client có thể tự cấu hình mà không cần hardcode URL.

Tóm lại:

  • OAuth 2.0: "Canva được phép đọc Google Drive của tôi."
  • OIDC: "Tôi là Nguyễn Văn A, email a@gmail.com — đây là chữ ký từ Google xác nhận điều đó."

Hầu hết các dịch vụ lớn (Google, GitHub, Microsoft, Apple) implement cả OAuth 2.0 lẫn OIDC trên cùng một endpoint. Khi bạn thêm openid vào scope, bạn đang dùng OIDC.

Lỗi bảo mật phổ biến

Triển khai OAuth 2.0 không đúng cách có thể dẫn đến các lỗ hổng nghiêm trọng. Dưới đây là những lỗi phổ biến nhất:

1. Không validate redirect_uri — Authorization Code Hijacking

Đây là lỗ hổng nguy hiểm nhất. Nếu Authorization Server không validate chặt chẽ redirect_uri, attacker có thể:

  1. Tạo link với redirect_uri=https://attacker.com/steal.
  2. Gửi link cho nạn nhân.
  3. Nạn nhân đăng nhập, authorization code được gửi đến attacker.com.
  4. Attacker đổi code lấy access token.

Nguyên tắc phòng chống:

  • So khớp chính xác (exact match) với URI đã đăng ký — không dùng wildcard, không chấp nhận subdomain mới.
  • Không chỉ kiểm tra prefix (https://myapp.com không đủ nếu attacker dùng https://myapp.com.evil.com).
  • Reject bất kỳ URI nào không có trong whitelist.

2. Bỏ qua tham số state — CSRF Attack

Tham số state là nonce ngẫu nhiên do client tạo ra, gửi trong authorization request và xác minh khi nhận callback. Nếu app không dùng hoặc không xác minh state:

  1. Attacker tạo authorization request của mình, lấy authorization code.
  2. Attacker nhúng code đó vào một trang, dụ nạn nhân vào.
  3. App của nạn nhân tự động trao đổi code của attacker, nối tài khoản attacker vào session của nạn nhân.

Luôn tạo state mới cho mỗi authorization request, lưu vào session, và xác minh trong callback trước khi trao đổi code.

3. Access token trong URL — Log Leakage

Implicit Flow (deprecated) và một số implementation kém cẩn thận đặt access token trong URL fragment hoặc query string. Token trong URL có thể bị lộ qua:

  • Browser history.
  • Referer header khi browser điều hướng đến trang khác.
  • Server access logs.
  • Proxy logs.

Luôn truyền token qua Authorization: Bearer header, không bao giờ trong URL.

4. Không dùng PKCE cho SPA/Mobile

Public clients (SPA, mobile) không thể bảo vệ client_secret. Nếu không có PKCE, authorization code có thể bị đánh chặn và đổi lấy token bởi bất kỳ app nào biết client_id (public information).

PKCE cho SPA và Mobile

PKCE (Proof Key for Code Exchange, RFC 7636) là giải pháp cho vấn đề trên. Ý tưởng: thay vì dùng client_secret cố định, mỗi authorization request tạo ra một code_verifier ngẫu nhiên và một code_challenge là hash của nó.

JavaScript
 1// SPA — PKCE flow (Web Crypto API)
 2async function generatePKCE() {
 3  const array = new Uint8Array(32);
 4  crypto.getRandomValues(array);
 5  const verifier = btoa(String.fromCharCode(...array))
 6    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
 7  const encoder = new TextEncoder();
 8  const data = encoder.encode(verifier);
 9  const digest = await crypto.subtle.digest('SHA-256', data);
10  const challenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
11    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
12  return { verifier, challenge };
13}

Luồng PKCE:

  1. SPA tạo code_verifier (chuỗi ngẫu nhiên 43–128 ký tự).
  2. SPA hash code_verifier bằng SHA-256 → code_challenge.
  3. SPA gửi code_challenge (không phải verifier!) cùng authorization request.
  4. Authorization Server lưu code_challenge.
  5. Khi SPA đổi code lấy token, gửi kèm code_verifier gốc.
  6. Authorization Server hash code_verifier, so sánh với code_challenge đã lưu — chỉ khớp nếu đúng là client đã bắt đầu flow.

Nếu attacker intercept authorization code, họ không có code_verifier → không thể đổi lấy token. PKCE biến mỗi authorization request thành một "one-time keypair" liên kết với client cụ thể.

Kể từ OAuth 2.1 (draft), PKCE là bắt buộc cho tất cả Authorization Code Flow, kể cả confidential clients.

Tóm tắt

OAuth 2.0 là xương sống của hệ sinh thái API hiện đại. Nắm vững các điểm sau:

  • OAuth 2.0 là ủy quyền (authorization), không phải xác thực — dùng OIDC khi cần "đăng nhập bằng Google".
  • 4 roles: Resource Owner (user), Client (app), Authorization Server (Google/GitHub), Resource Server (API).
  • Authorization Code Flow là flow khuyến nghị — code ngắn hạn, token trao đổi server-to-server.
  • PKCE là bắt buộc cho SPA và mobile — không dùng client_secret, dùng one-time verifier/challenge.
  • Scope thực thi least privilege — chỉ yêu cầu quyền thực sự cần.
  • Validate redirect_uri chặt chẽ, luôn dùng state, không để token trong URL.

2FA là gì? Bảo mật hai yếu tố

API là gì? REST API và cách hoạt động