What is OAuth 2.0? Authorization and Login with Google/GitHub
Security

What is OAuth 2.0? Authorization and Login with Google/GitHub

OAuth 2.0 is an open authorization protocol (RFC 6749) that lets third-party applications access user resources on another service without knowing their password. Learn the Authorization Code Flow, 4 roles, scopes, and how Google/GitHub login works under the hood.

In this series: Bảo mật
  1. 1 What is Malware? Classification, Characteristics, and Prevention
  2. 2 What is DDoS? Signs, Response and Effective Prevention Methods
  3. 3 What is Phishing? Recognizing and Preventing Online Fraud
  4. 4 What is DNS Sinkhole? Applications and How to Use DNS Sinkhole Technique
  5. 5 What is OAuth 2.0? Authorization and Login with Google/GitHub
  6. 6 What is a Trojan? Essential Information About Trojan Malware
  7. 7 What Is Zero Trust? The 'Never Trust, Always Verify' Security Model
  8. 8 What is VPN? Virtual Private Network, WireGuard and OpenVPN
  9. 9 What Is MFA? Multi-Factor Authentication vs 2FA Explained
  10. 10 What is a Firewall? Role and Functions in Network Security
  11. 11 What is SQL Injection? Database Attacks and Prevention
  12. 12 What is XSS? Cross-Site Scripting Attacks and Prevention
✦ Quick summary
OAuth 2.0 is an open authorization protocol (RFC 6749) that lets third-party applications access user resources on another service without knowing their password. Learn the Authorization Code Flow, 4...
How was this post?

OAuth 2.0 is an open authorization protocol (RFC 6749) that lets third-party applications access a user's resources on another service without knowing their password. It is the foundation of "Sign in with Google", "Login with GitHub", and countless API integrations used by billions of people every day.

In this article, we will explore how OAuth 2.0 works, distinguish authorization from authentication, walk through every step of the Authorization Code Flow, and understand why PKCE is essential for SPAs and mobile apps. By the end, you will know exactly what happens under the hood every time you click "Sign in with Google".

What is OAuth 2.0? Authorization vs Authentication

First, let us clear up two concepts that are commonly confused: authentication and authorization.

  • Authentication answers the question "Who are you?" — it verifies identity. When you enter a password, the system authenticates that you are the account owner.
  • Authorization answers the question "What are you allowed to do?" — it grants or restricts access after identity has been established.

OAuth 2.0 is an authorization protocol, not an authentication protocol. It solves the problem: how can an application (say, Canva) access your Google Drive without you handing over your Google password?

Consider a practical analogy: when you park at an upscale restaurant, the valet is given a valet key — a special key that only allows starting the engine and parking, not opening the glove box or the trunk. That is exactly how OAuth 2.0 works: instead of giving Canva full control over your Google account, you issue it a digital "valet key" — an access token — scoped only to reading Drive files, unable to read Gmail or change your password.

The OAuth 2.0 standard is defined in RFC 6749 (published October 2012) by the IETF. It is a full replacement for OAuth 1.0, simplifying the protocol and adding support for a wider range of client types.

Key properties of OAuth 2.0:

  • Applications receive a time-limited access token, never the password.
  • Users control scopes — the specific list of granted permissions.
  • Tokens can be revoked at any time without changing the password.
  • Supports multiple client types: web apps, mobile apps, SPAs, IoT devices.

The 4 Roles in OAuth 2.0

RFC 6749 defines four roles in a complete OAuth 2.0 flow. Understanding these roles clarifies who does what throughout the entire process.

1. Resource Owner

The Resource Owner is the end user — the entity that owns the resource and can grant access to it. In the "Canva accesses Google Drive" example, the Resource Owner is you — the person with the Google account who owns the Drive files.

The Resource Owner is not always a human. In machine-to-machine flows (Client Credentials), the Resource Owner and the Client may be the same entity.

2. Client

The Client is the application requesting access to the resource on behalf of the Resource Owner. In our example, Canva is the Client. The Client must register with the Authorization Server in advance to receive a client_id and client_secret.

There are two types of Clients:

  • Confidential client: A web app running on a server that can keep client_secret secure.
  • Public client: An SPA (React, Vue) or mobile app — cannot store secrets safely because the code runs on the user's device. Must use PKCE.

3. Authorization Server

The Authorization Server is the service that issues tokens after verifying the Resource Owner's identity and obtaining their consent. Google, GitHub, Facebook, and Microsoft each operate their own Authorization Servers.

The Authorization Server exposes two main endpoints:

  • Authorization Endpoint (/auth): receives the authorization request, displays the consent screen, returns an authorization code.
  • Token Endpoint (/token): receives the authorization code, validates the Client, returns an access token and refresh token.

4. Resource Server

The Resource Server is the API that holds the data the Client wants to access. It accepts the access token in the request header, validates it, and returns data if the token is valid and has the required scopes.

In practice, the Authorization Server and Resource Server are often deployed together (e.g., accounts.google.com issues tokens, www.googleapis.com is the Resource Server), but RFC 6749 separates them conceptually.

Authorization Code Flow — Step by Step

Authorization Code Flow is the most widely used grant type, recommended for web apps and mobile apps. It is the most secure flow because the access token never passes through the browser.

1. User clicks "Login with Google" in the App
2. App redirects → 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 logs in and consents → Google redirects back to:
       https://myapp.com/callback?code=AUTH_CODE&state=TOKEN

4. App backend POSTs to Google:
       https://oauth2.googleapis.com/token
       client_id, client_secret, code, redirect_uri, grant_type=authorization_code

5. Google responds with:
       { access_token, expires_in, refresh_token, id_token }

6. App calls the API:
       GET https://www.googleapis.com/oauth2/v3/userinfo
       Authorization: Bearer ACCESS_TOKEN

Breaking down each step:

Steps 1–2: Authorization Request The app constructs a redirect URL with these parameters:

  • client_id: the app identifier registered with Google.
  • redirect_uri: the URL Google will redirect back to after user consent (must match the registered URI exactly).
  • response_type=code: specifies the Authorization Code Flow.
  • scope: the list of permissions being requested.
  • state: a random nonce to prevent CSRF — the app must verify this value when it receives the callback.

Step 3: Authorization Code After the user logs in and consents on Google's consent screen, Google redirects back to redirect_uri with a code — an authorization code that lives for roughly 10 seconds and can only be used once. This is a critical security property: the short-lived, single-use code prevents replay attacks.

Step 4: Token Exchange The app backend (not the browser!) sends a POST request to Google's Token Endpoint, including the client_secret. This exchange happens directly between servers over HTTPS, never through the browser — which is why the access token never appears in browser history or logs.

Step 5: Token Response Google returns:

  • access_token: the token used to call APIs, typically expiring in 1 hour.
  • expires_in: seconds until expiry.
  • refresh_token: a long-lived token used to obtain a new access token without user interaction (only issued when offline_access scope is requested).
  • id_token: a JWT containing user identity claims (only present when using OIDC with the openid scope).

Step 6: API Call The app includes the access token in the Authorization: Bearer <token> header when calling the Resource Server. This is the standard Bearer Token syntax defined in RFC 6750.

Grant Types

OAuth 2.0 defines multiple grant types for different use cases. Each grant type is designed for a specific kind of client and scenario.

As described above, this is the most secure grant type for:

  • Web apps (confidential clients): use client_secret on the server.
  • SPAs and mobile apps (public clients): use PKCE instead of client_secret.

Client Credentials

Used for machine-to-machine (M2M) flows — when no human user is involved. For example, microservice A needs to call microservice B's API.

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

Service A authenticates itself with client_id + client_secret and receives an access token directly, with no consent screen involved.

Device Authorization Grant (Device Code)

Used for devices without a browser or with limited input: Smart TVs, game consoles, IoT devices, CLI tools. The device displays a short URL and a verification code; the user enters the URL on their phone or computer, logs in and consents; the device polls the Authorization Server until it receives a token.

Implicit Flow (Deprecated)

Implicit Flow was once used for SPAs before CORS became widely supported. It returned the access token directly in the URL fragment, skipping the code exchange step — convenient but insecure (token exposed in browser history, referrer headers). Implicit Flow is no longer recommended; OAuth 2.1 (draft) removes it entirely. Use Authorization Code + PKCE for SPAs.

OAuth 2.0 vs OpenID Connect (OIDC)

This is one of the most misunderstood aspects of OAuth 2.0: when you "Sign in with Google", you are not using OAuth 2.0 alone — you are using OpenID Connect (OIDC).

Pure OAuth 2.0 solves the authorization problem: granting an app permission to access resources. It does not provide a standard mechanism for answering "who is this user?" An OAuth 2.0 access token is an opaque string — the Resource Server knows the token is valid, but without additional information, it does not know which user it belongs to.

OpenID Connect (OIDC) is an authentication layer built on top of OAuth 2.0, specified by the OpenID Foundation. OIDC adds:

  1. ID Token: A JWT returned alongside the access token, containing sub (subject/user ID), name, email, picture, and other user identity claims. The Client can read the ID Token without an extra API call.

  2. /userinfo Endpoint: A standardized Resource Server endpoint — call it with an access token to retrieve user information as JSON.

  3. openid Scope: A special scope that activates OIDC. When the authorization request includes scope=openid, the Authorization Server returns an ID Token.

  4. Discovery Document: The /.well-known/openid-configuration endpoint returns JSON describing all endpoints, supported scopes, and signing keys — clients can auto-configure without hardcoding URLs.

In summary:

  • OAuth 2.0: "Canva is allowed to read my Google Drive."
  • OIDC: "I am John Smith, email j@gmail.com — here is Google's cryptographic signature confirming that."

Most major identity providers (Google, GitHub, Microsoft, Apple) implement both OAuth 2.0 and OIDC on the same endpoint. Adding openid to the scope switches you into OIDC mode.

Common Security Mistakes

Implementing OAuth 2.0 incorrectly can introduce serious vulnerabilities. Here are the most common mistakes:

1. Not Validating redirect_uri — Authorization Code Hijacking

This is the most dangerous vulnerability. If the Authorization Server does not strictly validate redirect_uri, an attacker can:

  1. Craft a link with redirect_uri=https://attacker.com/steal.
  2. Send the link to a victim.
  3. The victim logs in; the authorization code is delivered to attacker.com.
  4. The attacker exchanges the code for an access token.

Prevention rules:

  • Exact match against the registered URI — no wildcards, no new subdomains.
  • Never prefix-only checking (https://myapp.com is not safe if an attacker uses https://myapp.com.evil.com).
  • Reject any URI not present in the whitelist.

2. Ignoring the state Parameter — CSRF Attack

The state parameter is a random nonce created by the client, sent in the authorization request, and verified on callback. If the app does not use or verify state:

  1. Attacker crafts their own authorization request and obtains an authorization code.
  2. Attacker embeds the code in a page and tricks the victim into visiting it.
  3. The victim's app automatically exchanges the attacker's code, binding the attacker's account to the victim's session.

Always generate a fresh state for each authorization request, store it in the session, and verify it in the callback before exchanging the code.

3. Access Tokens in URLs — Log Leakage

The deprecated Implicit Flow and some careless implementations place access tokens in URL fragments or query strings. Tokens in URLs can leak through:

  • Browser history.
  • The Referer header when the browser navigates elsewhere.
  • Server access logs.
  • Proxy logs.

Always transmit tokens in the Authorization: Bearer header, never in the URL.

4. Not Using PKCE for SPAs and Mobile Apps

Public clients (SPAs, mobile) cannot protect a client_secret. Without PKCE, an intercepted authorization code can be exchanged for a token by any app that knows the client_id (which is public).

PKCE for SPAs and Mobile Apps

PKCE (Proof Key for Code Exchange, RFC 7636) solves this problem. The idea: instead of a static client_secret, each authorization request generates a random code_verifier and a code_challenge that is a hash of it.

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}

The PKCE flow:

  1. The SPA generates a code_verifier (a random string of 43–128 characters).
  2. The SPA hashes code_verifier using SHA-256 → code_challenge.
  3. The SPA sends code_challenge (not the verifier!) with the authorization request.
  4. The Authorization Server stores the code_challenge.
  5. When the SPA exchanges the code for a token, it sends the original code_verifier.
  6. The Authorization Server hashes code_verifier and compares it to the stored code_challenge — they match only if the request came from the same client that initiated the flow.

If an attacker intercepts the authorization code, they do not have code_verifier → they cannot exchange it for a token. PKCE turns each authorization request into a one-time keypair bound to a specific client instance.

As of OAuth 2.1 (draft), PKCE is mandatory for all Authorization Code Flows, including confidential clients.

Summary

OAuth 2.0 is the backbone of the modern API ecosystem. Keep these key points in mind:

  • OAuth 2.0 is authorization, not authentication — use OIDC when you need "Sign in with Google".
  • 4 roles: Resource Owner (user), Client (app), Authorization Server (Google/GitHub), Resource Server (API).
  • Authorization Code Flow is the recommended flow — short-lived code, token exchanged server-to-server.
  • PKCE is mandatory for SPAs and mobile — no client_secret, use a one-time verifier/challenge pair instead.
  • Scopes enforce least privilege — only request what you actually need.
  • Validate redirect_uri strictly, always use state, never put tokens in URLs.

What is JWT? JSON Web Token and API Authentication

What is 2FA? Two-Factor Authentication Explained

What is an API? REST API and How It Works

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