JWT là gì? Hướng dẫn rõ ràng về JSON Web Tokens
Tìm hiểu JWT (JSON Web Token) là gì, ba phần của nó hoạt động ra sao, nơi sử dụng và các mẹo bảo mật để tránh sai lầm phổ biến liên quan đến token.

JWT in simple terms
A JWT (JSON Web Token) is a compact, URL-safe string that represents a set of information (usually about a user or session) in a way that can be passed between systems. You’ll often see it as a long value starting with something like eyJ..., sent in an HTTP header such as Authorization: Bearer \u003ctoken\u003e.
Why use a token at all?
Traditional logins often rely on server sessions: after you sign in, the server stores session data and gives the browser a session ID cookie. Every request includes that cookie, and the server looks up the session.
With token-based auth, the server can avoid keeping session state for every user request. Instead, the client holds a token (like a JWT) and includes it with API calls. This is popular for APIs because it:
- works well across multiple services (API gateways, microservices)
- fits mobile and single-page apps (SPAs) that call APIs directly
- reduces the need for shared session storage across servers
Important nuance: “stateless” doesn’t mean “no server-side checks ever.” Many real systems still validate tokens against user status, rotating keys, or revocation mechanisms.
Authentication vs authorization (plain English)
- Authentication answers: Who are you? (You sign in and prove your identity.)
- Authorization answers: What are you allowed to do? (You can read invoices, edit projects, access admin pages, etc.)
JWTs commonly carry proof of authentication (you’re signed in) and basic authorization hints (roles, permissions, scopes)—but your server should still enforce authorization rules.
Where JWTs show up
You’ll commonly see JWTs used as access tokens in:
- web APIs
- SPAs
- mobile apps
- systems using OAuth 2.0 or OpenID Connect (OIDC)
JWT structure: header, payload, and signature
A JWT is a compact string made of three parts, each base64url-encoded and separated by dots:
header.payload.signature
Example (redacted):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNzAwMDAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c…
1) Header
The header describes how the token was created—most importantly the signing algorithm (e.g., HS256, RS256/ES256) and the token type.
Common fields:
typ: often"JWT"(frequently ignored in practice)alg: the signing algorithm usedkid: a key identifier to help the verifier select the right key during rotation
Security note: don’t trust the header blindly. Enforce an allowlist of algorithms you actually use, and do not accept alg: "none".
2) Payload
The payload holds “claims” (fields) about the user and token context: who it’s for, who issued it, and when it expires.
Important: JWTs are not encrypted by default. Base64url encoding makes the token URL-safe; it does not hide the data. Anyone who gets the token can decode the header and payload.
That’s why you should avoid putting secrets (passwords, API keys) or sensitive personal data into a JWT.
3) Signature
The signature is created by signing the header + payload with a key:
- HS256: a shared secret signs and verifies
- RS256/ES256: a private key signs; a public key verifies
The signature provides integrity: it lets the server verify the token wasn’t modified and was minted by a trusted signer. It does not provide confidentiality.
Size considerations
Because a JWT includes header and payload on every request where it’s sent, bigger tokens mean more bandwidth and overhead. Keep claims lean and prefer identifiers over bulky data.
Payload and claims: what you can (and shouldn’t) store
Claims generally fall into two buckets: registered (standardized names) and custom (your app’s fields).
Common registered claims
iss(issuer): who created the tokensub(subject): who the token is about (often a user ID)aud(audience): who the token is meant for (e.g., a specific API)exp(expiration time): when the token must stop being acceptediat(issued at): when the token was creatednbf(not before): the token should not be accepted before this time
Custom claims: keep them minimal
Include only what the receiving service truly needs to make an authorization decision.
Good examples:
- a stable internal user identifier (
user_id) - a small set of roles/permissions (only if you can keep them current)
- a tenant/organization ID in multi-tenant apps
Avoid “convenience claims” that duplicate lots of profile data. They bloat the token, go stale quickly, and increase impact if the token leaks.
What you should never put in a JWT payload
Because the payload is readable, don’t store:
- passwords, API keys, refresh tokens, or any secret value
- payment details, government IDs, or sensitive personal data
- anything you wouldn’t want copied from a browser, proxy, or log
If you need sensitive information, store it server-side and put only a reference (like an ID) in the token—or use an encrypted token format (JWE) when appropriate.
How the signature works (and what it guarantees)
Signing is not encryption.
- Signing is like sealing a letter: people can read it, but they can verify it wasn’t altered.
- Encryption is like locking the letter in a box: only someone with the key can read it.
When a JWT is issued, the server signs the encoded header + payload. When the token is presented later, the server recomputes the signature and compares it. If someone changes even one character (e.g., "role":"user" to "role":"admin"), verification fails and the token is rejected.
JWT vs OAuth, OpenID Connect, and token types
JWT is a token format. OAuth 2.0 and OpenID Connect (OIDC) are protocols that describe how apps request, issue, and use tokens.
OAuth 2.0 and access/refresh tokens
OAuth 2.0 is mainly about authorization: letting an app access an API on a user’s behalf without sharing the user’s password.
- Access token: presented to an API to prove permission; may be a JWT or an opaque token
- Refresh token: longer-lived token used to obtain new access tokens
Access tokens are typically short-lived (minutes). Short lifetimes limit damage if a token leaks.
OpenID Connect (OIDC) and ID tokens
OIDC adds authentication (who the user is) on top of OAuth 2.0 and introduces an ID token, which is usually a JWT.
- ID token: for the client app to confirm the user’s identity
- Access token: for the API to authorize requests
A key rule: don’t use an ID token to call an API.
If you want more context on practical flows, see a post about the JWT authentication flow.
Common JWT authentication flow
A typical flow looks like this:
1) Login
The user signs in (email/password, SSO, etc.). If successful, the server creates a JWT (often an access token) with essential claims like subject and expiration.
2) Token issuance
The server signs the token and returns it to the client (web app, mobile app, or another service).
3) API calls
For protected endpoints, the client includes the JWT in the Authorization header:
Authorization: Bearer \u003cJWT\u003e
4) Verification
Before serving the request, the API typically checks:
- signature (integrity + trusted issuer)
exp(not expired)iss(expected issuer)aud(intended for this API)
If all checks pass, the API treats the user as authenticated and applies authorization rules (e.g., record-level permissions).
5) A quick note on clock skew
Because system clocks drift, many systems allow small clock skew when validating time-based claims like exp (and sometimes nbf). Keep the skew small to avoid extending token validity more than intended.
Where to store JWTs safely
Storage choices change what attackers can steal and how easily they can replay a token.
Browser apps: memory vs localStorage vs cookies
In-memory storage (often recommended for SPAs) keeps the access token in JS state. It clears on refresh and reduces “grab it later” theft, but an XSS bug can still read it while the page runs. Pair it with short-lived access tokens and a refresh flow.
localStorage/sessionStorage are easy but risky: any XSS vulnerability can exfiltrate tokens from web storage. If you use them, treat XSS prevention as non-negotiable (CSP, output escaping, dependency hygiene) and keep tokens short-lived.
Secure cookies (often the safest default for web) store tokens in an HttpOnly cookie so JavaScript can’t read them—reducing the impact of XSS token theft. The trade-off is CSRF risk, since browsers attach cookies automatically.
If you use cookies, set:
HttpOnlySecure(HTTPS only)SameSite=LaxorSameSite=Strict(some cross-site flows may needSameSite=None; Secure)
Also consider CSRF tokens for state-changing requests.
Mobile apps: prefer OS secure storage
On iOS/Android, store tokens in platform secure storage (Keychain / Keystore-backed storage). Avoid plain files or preferences. If your threat model includes rooted/jailbroken devices, assume extraction is possible and rely on short-lived tokens and server-side controls.
Least privilege
Limit what a token can do: use minimal scopes/claims, keep access tokens short-lived, and avoid embedding sensitive data.
Common JWT security pitfalls to avoid
JWTs are convenient, but many incidents come from predictable mistakes. Treat a JWT like cash: whoever gets it can often spend it.
1) Overly long expirations
If a token lasts days or weeks, a leak gives an attacker that entire window.
Prefer short-lived access tokens (minutes) and refresh them through a safer mechanism. If you need “remember me,” do it with refresh tokens and server-side controls.
2) Skipping issuer and audience checks
Valid signatures aren’t enough. Verify iss and aud, and validate time-based claims like exp and nbf.
3) Trusting decoded payloads
Decoding is not verification. Always verify the signature on the server and enforce permissions server-side.
4) Algorithm confusion and key mix-ups
- Don’t accept whatever algorithm a token claims. Allowlist expected algorithms.
- Don’t mix up symmetric keys (HS256) with public/private keys (RS256/ES256).
- Minimize blast radius by separating keys per environment and rotating them.
5) Token leakage via URLs, logs, and referrers
Avoid putting JWTs in query params. They can end up in browser history, server logs, analytics tools, and referrer headers.
Use Authorization: Bearer ... instead.
6) No key rotation or revocation plan
Assume keys and tokens can leak. Rotate signing keys, use kid to support smooth rotation, and have a revocation strategy (short expirations + ability to disable accounts/sessions). For storage guidance, see a post about where to store JWTs safely.
When to use JWT (and when not to)
JWTs are useful, but they aren’t automatically the best choice. The real question is whether you benefit from a self-contained token that can be verified without a database lookup on every request.
Good fits for JWT
- Stateless APIs at scale: local verification (signature + expiry) without per-request session lookups
- Multiple services / microservices: shared verification rules and public keys
- SPAs and mobile apps: clients calling APIs directly
- Short-lived access tokens: reduced impact of theft
When JWT is a poor fit
- Instant revocation is a must: sessions are simpler if you need “log out everywhere now” without extra infrastructure
- You need to carry sensitive data: typical JWTs are signed, not encrypted
- Long-lived tokens: they’re high-value and worth stealing
When simple session cookies are better
For traditional server-rendered web apps where straightforward invalidation matters, server-side sessions with HttpOnly cookies are often the simpler, safer default.
Quick decision checklist
Choose JWT if you need stateless verification across services and can keep tokens short-lived.
Avoid JWT if you need instant revocation, plan to store sensitive data in the token, or can use session cookies without friction.
Practical checklist and FAQs
Verification checklist (what to check every time)
- Signature is valid
Verify using the correct key and expected algorithm. Reject invalid signatures—no exceptions.
exp(expiration)
Ensure the token hasn’t expired.
nbf(not before)
If present, ensure the token isn’t being used too early.
aud(audience)
Confirm the token was meant for your API/service.
iss(issuer)
Confirm the token came from the expected issuer.
- Sanity checks (recommended)
Validate token format, enforce maximum size, and reject unexpected claim types to reduce edge-case bugs.
Choosing HS256 vs RS256/ES256
-
HS256 (symmetric key): one shared secret signs and verifies.
- Good for: a single app/API controlled by one team.
- Watch out for: any verifier that has the secret can also mint tokens.
-
RS256 / ES256 (asymmetric keys): private key signs; public key verifies.
- Good for: multiple services verifying tokens; distributing public keys without enabling signing.
- Operational note: rotation is often safer because only the signer holds the private key.
Rule of thumb: if more than one independent system needs to verify tokens (or you don’t fully trust every verifier), prefer RS256/ES256.
Monitoring and logging (without leaking tokens)
- Don’t log raw tokens (headers, cookies, query strings).
- If you need correlation, log a token fingerprint (e.g., a hash) or safe metadata (
iss,aud, and a user ID only if policy allows). - Watch for anomalies: signature failures, spikes in expired tokens, unusual audiences/issuers, and suspicious refresh patterns.
FAQs
Is JWT encrypted?
Not by default. Most JWTs are signed, not encrypted, meaning the contents can be read by anyone who has the token. Use JWE or keep sensitive data out of JWTs.
Can I revoke a JWT?
Not easily if you rely only on self-contained access tokens. Common approaches include short-lived access tokens, deny-lists for high-risk events, or refresh tokens with rotation.
How long should exp be?
As short as your UX and architecture allow. Many APIs use minutes for access tokens, paired with refresh tokens for longer sessions.
Building JWT-protected apps faster with Koder.ai
If you’re implementing JWT auth in a new API or SPA, a lot of the work is repetitive: wiring middleware, validating iss/aud/exp, setting cookie flags, and keeping token handling out of logs.
With Koder.ai, you can vibe-code a web app (React), backend services (Go + PostgreSQL), or a Flutter mobile app through a chat-driven workflow—then iterate in a planning mode, use snapshots and rollback as you refine security, and export the source code when you’re ready. It’s a practical way to accelerate building JWT-based authentication flows while still keeping control over verification logic, key rotation strategy, and deployment/hosting settings (including custom domains).
Câu hỏi thường gặp
What is a JWT, and where do I usually send it?
Một JWT (JSON Web Token) là một chuỗi ngắn, an toàn với URL, mang các claim (các trường dữ liệu) và có thể được server xác minh. Thông thường nó được gửi trong yêu cầu API qua:
Authorization: Bearer \u003ctoken\u003e
Ý chính: server có thể xác thực tính toàn vẹn của token (thông qua chữ ký) mà không cần lưu một bản ghi session cho từng người dùng trên mỗi yêu cầu.
How is JWT authentication different from server sessions?
Xác thực bằng session thường lưu trạng thái trên server (một bản ghi session được tham chiếu bằng cookie/session ID). Với xác thực dựa trên JWT, client gửi một token được ký trong mỗi yêu cầu và API sẽ xác thực token đó.
JWT phổ biến cho API và kiến trúc đa dịch vụ vì việc xác thực có thể thực hiện cục bộ, giảm nhu cầu lưu session chia sẻ.
“Không trạng thái” (stateless) vẫn thường bao gồm các kiểm tra phía server như danh sách thu hồi, kiểm tra trạng thái người dùng hoặc xoay khóa.
What are the three parts of a JWT (header, payload, signature)?
Một JWT gồm ba phần mã hóa Base64URL, cách nhau bởi dấu chấm:
header.payload.signature
Header mô tả cách token được ký, payload chứa các claim (như sub, exp, aud), và signature cho phép server phát hiện việc thay đổi.
Is a JWT encrypted, and can people read what’s inside?
Không. JWT tiêu chuẩn thường được ký, không phải mã hóa.
- Việc ký đảm bảo tính toàn vẹn (không bị sửa đổi) và tính xác thực (được phát bởi bên ký đáng tin cậy).
- Bất kỳ ai có token đều có thể decode Base64URL để đọc header và payload.
Nếu cần bảo mật nội dung, hãy xem xét JWE (token được mã hóa) hoặc giữ thông tin nhạy cảm ở phía server và chỉ để một định danh trong JWT.
What does the JWT signature guarantee—and what doesn’t it guarantee?
Chữ ký cho phép server xác minh token không bị thay đổi và được tạo bởi người có khóa ký.
Nó không:
- che giấu nội dung payload
- chứng minh người dùng vẫn đang hoạt động (trừ khi bạn kiểm tra thêm)
- thu hồi token trước
expmột cách tự động
Hãy coi token như một chứng chỉ: nếu bị lộ, nó có thể bị sử dụng lại cho đến khi hết hạn.
What are `alg` and `kid` in the JWT header, and why do they matter?
alg cho biết thuật toán được dùng (ví dụ HS256 vs RS256). kid là bộ nhận diện khóa giúp chọn khóa xác minh đúng khi xoay khóa.
Quy tắc bảo mật cơ bản:
- Dùng danh sách cho phép (allowlist) các thuật toán mong đợi; đừng chấp nhận mọi giá trị
alg. - Không bao giờ chấp nhận
alg: "none". - Đừng để giá trị
kidkhông đáng tin khiến quá trình tra cứu khóa trở nên không an toàn.
Which JWT claims should I include in the payload?
Bắt đầu với các claim tiêu chuẩn và giữ các claim tùy chỉnh ở mức tối thiểu.
Claim thường dùng:
iss(issuer)sub(subject / định danh người dùng)aud(audience / API đích)exp(expiration)iat(issued at)nbf(not before)
Tránh đặt bí mật hoặc dữ liệu cá nhân nhạy cảm trong payload vì nó có thể bị đọc nếu token bị lộ.
How do JWT, OAuth 2.0, and OpenID Connect relate (access tokens vs ID tokens)?
JWT là một định dạng token; OAuth 2.0 và OpenID Connect là giao thức.
Phân biệt thông dụng:
- Access token: dùng để gọi API (có thể là JWT hoặc opaque).
- ID token (OIDC): dùng bởi client app để xác nhận danh tính (thường là JWT).
- Refresh token: dùng để lấy access token mới (thường opaque và rất nhạy cảm).
Quan trọng: đừng dùng ID token để gọi API chỉ vì nó trông giống access token JWT.
Where should I store JWTs safely in a browser app?
Với app web, các lựa chọn thường là:
- Lưu trong bộ nhớ (in-memory): được khuyến nghị cho SPA — nó bị xóa khi refresh và giảm rủi ro bị “lấy sau”, nhưng XSS vẫn có thể đọc token khi trang đang chạy. Kết hợp với token ngắn hạn và flow refresh.
- localStorage/sessionStorage: dễ dùng nhưng rủi ro nếu có XSS. Nếu dùng, phòng ngừa XSS là bắt buộc (CSP, escape output, quản lý dependency) và giữ token ngắn hạn.
- Cookie HttpOnly an toàn: thường an toàn hơn vì JS không đọc được cookie — giảm tác hại từ XSS. Đổi lại, có rủi ro CSRF vì trình duyệt tự động gửi cookie.
Nếu dùng cookie, set:
HttpOnlySecure(chỉ HTTPS)SameSite=LaxhoặcSameSite=Strict(một số luồng cross-site cầnSameSite=None; Secure)
Cân nhắc token CSRF cho các request thay đổi trạng thái.
What checks should my API perform when validating a JWT?
Ít nhất, API nên kiểm tra:
- chữ ký (với khóa đúng và thuật toán được allowlist)
exp(chưa hết hạn)iss(issuer đúng)aud(dành cho API của bạn)nbf(nếu có)
Thêm các biện pháp thực tế:
- giới hạn kích thước token tối đa
- từ chối các kiểu claim bất thường
- cho phép một khoảng sai số nhỏ về đồng hồ để tránh vấn đề lệch giờ