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.

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.
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:
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.
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.
You’ll commonly see JWTs used as access tokens in:
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…
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 rotationSecurity note: don’t trust the header blindly. Enforce an allowlist of algorithms you actually use, and do not accept alg: "none".
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.
The signature is created by signing the header + payload with a key:
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.
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.
Claims generally fall into two buckets: registered (standardized names) and custom (your app’s fields).
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 timeInclude only what the receiving service truly needs to make an authorization decision.
Good examples:
user_id)Avoid “convenience claims” that duplicate lots of profile data. They bloat the token, go stale quickly, and increase impact if the token leaks.
Because the payload is readable, don’t store:
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.
Signing is not encryption.
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 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 is mainly about authorization: letting an app access an API on a user’s behalf without sharing the user’s password.
Access tokens are typically short-lived (minutes). Short lifetimes limit damage if a token leaks.
OIDC adds authentication (who the user is) on top of OAuth 2.0 and introduces an ID token, which is usually a JWT.
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.
A typical flow looks like this:
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.
The server signs the token and returns it to the client (web app, mobile app, or another service).
For protected endpoints, the client includes the JWT in the Authorization header:
Authorization: Bearer \u003cJWT\u003e
Before serving the request, the API typically checks:
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).
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.
Storage choices change what attackers can steal and how easily they can replay a token.
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=Lax or SameSite=Strict (some cross-site flows may need SameSite=None; Secure)Also consider CSRF tokens for state-changing requests.
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.
Limit what a token can do: use minimal scopes/claims, keep access tokens short-lived, and avoid embedding sensitive data.
JWTs are convenient, but many incidents come from predictable mistakes. Treat a JWT like cash: whoever gets it can often spend it.
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.
Valid signatures aren’t enough. Verify iss and aud, and validate time-based claims like exp and nbf.
Decoding is not verification. Always verify the signature on the server and enforce permissions server-side.
Avoid putting JWTs in query params. They can end up in browser history, server logs, analytics tools, and referrer headers.
Use Authorization: Bearer ... instead.
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.
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.
For traditional server-rendered web apps where straightforward invalidation matters, server-side sessions with HttpOnly cookies are often the simpler, safer default.
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.
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.
Validate token format, enforce maximum size, and reject unexpected claim types to reduce edge-case bugs.
HS256 (symmetric key): one shared secret signs and verifies.
RS256 / ES256 (asymmetric keys): private key signs; public key verifies.
Rule of thumb: if more than one independent system needs to verify tokens (or you don’t fully trust every verifier), prefer RS256/ES256.
iss, aud, and a user ID only if policy allows).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.
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).
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.
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.
Một JWT gồm ba phần mã hóa Base64URL, cách nhau bởi dấu chấm:
header.payload.signatureHeader 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.
Không. JWT tiêu chuẩn thường được ký, không phải mã hóa.
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.
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:
exp một cách tự độngHã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.
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:
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:
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:
Với app web, các lựa chọn thường là:
Nếu dùng cookie, set:
Ít nhất, API nên kiểm tra:
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ế:
alg.alg: "none".kid không đáng tin khiến quá trình tra cứu khóa trở nên không an toàn.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ộ.
Quan trọng: đừng dùng ID token để gọi API chỉ vì nó trông giống access token JWT.
HttpOnlySecure (chỉ HTTPS)SameSite=Lax hoặc SameSite=Strict (một số luồng cross-site cần SameSite=None; Secure)Cân nhắc token CSRF cho các request thay đổi trạng thái.