Complete Guide to JWT Authentication: Principles, Usage, and Security Best Practices
After you log in to a web application, how does the server know that each subsequent request is made by "you"? HTTP is a stateless protocol, and each request is inherently independent, with no "memory". There are two mainstream solutions to this problem: Session Token and JWT.
JWT (JSON Web Token) has almost become the standard choice in the REST API field in recent years, widely adopted by mainstream platforms such as GitHub, Auth0, and Google. This article systematically explains everything about JWT from principles to practice.
What is JWT?
JWT is a compact, self-contained token format used to securely transmit JSON-formatted claims information between parties.
"Self-contained" is a core feature of JWT: the token itself carries user information and verification information, allowing the server to verify the token's legitimacy without querying the database. This makes JWT naturally suitable for stateless distributed architectures.
Detailed Structure of JWT
A JWT consists of three parts, separated by .:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuW8oOS4iSIsImlhdCI6MTUxNjIzOTAyMn0
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Part 1: Header
After Base64URL decoding:
{
"alg": "HS256",
"typ": "JWT"
}
alg: Signature algorithm (HS256, RS256, ES256, etc.)typ: Token type, fixed asJWT
Part 2: Payload
After Base64URL decoding:
{
"sub": "1234567890",
"name": "张三",
"email": "zhangsan@example.com",
"role": "admin",
"iat": 1516239022,
"exp": 1516325422
}
Payload contains "claims", divided into three categories:
Registered Claims:
| Field | Full Name | Meaning |
|---|---|---|
sub |
Subject | Token subject (usually user ID) |
iss |
Issuer | Token issuer |
aud |
Audience | Token audience (target service) |
exp |
Expiration Time | Expiration time (Unix timestamp) |
iat |
Issued At | Issuance time |
nbf |
Not Before | Effective time (invalid before) |
Custom Claims: Any fields required by the business can be added, such as role, email, permissions, etc.
⚠️ Important Warning: Payload is Base64URL encoded, not encrypted! Anyone can decode and view the content. Never store sensitive data like passwords or credit card numbers in the Payload.
Part 3: Signature
The signature calculation method:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Taking HS256 as an example, the server uses a secret known only to itself to perform HMAC-SHA256 hash operation on the first two parts to generate the signature.
Verification Mechanism: When the server receives a request, it recalculates the signature using the same algorithm and compares it with the signature in the token. As long as the secret is not leaked, anyone who tampers with the Header or Payload will have a mismatched signature—because they don't have the secret and cannot generate a valid signature.
JWT vs Session Token Comparison
| Dimension | JWT | Session Token |
|---|---|---|
| Storage Location | Client-side | Server-side (Database/Redis) |
| Server State | Stateless | Stateful |
| Scalability | Naturally supports horizontal scaling | Requires session sharing (Redis) |
| Logout Implementation | Difficult (requires blacklist mechanism) | Simple (delete server-side session) |
| Performance | Verification requires no DB query | Requires query to session storage each time |
| Token Size | Larger (typically 500B~2KB) | Very small (only session ID) |
| Security | Cannot revoke before expiration (default) | Can revoke immediately |
| Applicable Scenarios | Distributed, microservices, APIs | Traditional web applications |
Complete JWT Usage Flow
1. 用户登录(POST /auth/login,发送账号密码)
↓
2. 服务端验证凭据,生成 JWT
- Header: { alg: "HS256", typ: "JWT" }
- Payload: { sub: "user_123", role: "admin", exp: now+7days }
- 用 secret 签名
↓
3. 服务端返回 JWT 给客户端
↓
4. 客户端存储 JWT(LocalStorage 或 Cookie)
↓
5. 客户端每次请求携带 JWT
Authorization: Bearer <token>
↓
6. 服务端验证 JWT
- 检查签名是否合法
- 检查 exp 是否过期
- 从 Payload 提取用户信息,处理业务逻辑
Server-side JWT generation code example (Node.js):
import jwt from 'jsonwebtoken';
// 生成 JWT
function generateToken(userId, role) {
return jwt.sign(
{
sub: userId,
role: role,
},
process.env.JWT_SECRET,
{
expiresIn: '7d', // 7 天过期
issuer: 'myapp.com',
}
);
}
// 验证 JWT(中间件)
function verifyToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
const token = authHeader.slice(7);
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
Security Analysis of Token Storage Locations
This is one of the most controversial topics in JWT:
localStorage / sessionStorage
// 存储
localStorage.setItem('token', jwt);
// 使用
const token = localStorage.getItem('token');
fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` }
});
Advantages: Simple and direct, cross-subdomain access Disadvantages: Risk of XSS attacks—if the page has an XSS vulnerability, attackers can read the token from localStorage via injected scripts
HttpOnly Cookie (Recommended)
// 服务端设置 Cookie(Node.js/Express)
res.cookie('access_token', jwt, {
httpOnly: true, // JavaScript 无法读取,防 XSS
secure: true, // 仅 HTTPS 传输
sameSite: 'strict', // 防 CSRF
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 天
});
Advantages: JavaScript cannot read (prevents XSS), browser automatically carries it
Disadvantages: Requires handling CSRF (use sameSite: 'strict' or CSRF token protection); not suitable for mobile apps
Conclusion: For web applications, prioritize HttpOnly cookies; for mobile apps or third-party API clients, use localStorage (while strictly guarding against XSS).
Security Precautions
1. Set Reasonable Expiration Times
// Access Token:短生命周期(15分钟~1小时)
jwt.sign(payload, secret, { expiresIn: '15m' });
// Refresh Token:较长生命周期(7~30天),存储在服务端
jwt.sign({ sub: userId }, refreshSecret, { expiresIn: '30d' });
2. Refresh Token Mechanism
Set short expiration for Access Token, use Refresh Token to obtain new Access Token, balancing security and user experience:
Access Token 过期(返回 401)
↓
客户端用 Refresh Token 请求 /auth/refresh
↓
服务端验证 Refresh Token(查数据库,确认未被吊销)
↓
返回新的 Access Token(+ 可选:轮换 Refresh Token)
3. Use HTTPS
JWT is transmitted in plaintext in the header; without HTTPS, it's exposed.
4. Beware of Algorithm Confusion Attacks (alg: none vulnerability)
Early JWT libraries had serious vulnerabilities: attackers modified the alg in the header to none, causing the library to skip signature verification.
// 攻击者篡改的 Header
{ "alg": "none", "typ": "JWT" }
Protection: Use the latest version of JWT libraries; explicitly specify allowed algorithms during verification:
// ✅ 显式指定算法,拒绝其他算法
jwt.verify(token, secret, { algorithms: ['HS256'] });
5. Choosing Between HS256 and RS256
| HS256 | RS256 | |
|---|---|---|
| Algorithm | HMAC-SHA256 (symmetric) | RSA-SHA256 (asymmetric) |
| Signature Key | Single secret | Private key for signing / Public key for verification |
| Verification Method | Requires holding the secret | Only requires public key, which can be public |
| Suitable Scenarios | Single service, internal systems | Microservices, third-party verification (e.g., Auth0) |
FAQ
Q: Can JWT be logged out?
This is an inherent limitation of JWT. Since JWT is stateless, the server by default does not maintain token state, and the token remains valid until exp expires. There are two solutions for logout:
- Token Blacklist: When logging out, store the token's
jti(JWT ID) in Redis, and check the blacklist during verification. Disadvantage: Introduces state, increases Redis queries, partially negating JWT's stateless advantage. - Short Expiration + Refresh Token Rotation: Access Token expires in 15 minutes; when logging out, revoke the Refresh Token (delete on server), so the user can still access for up to 15 minutes but cannot renew. This is the most commonly used compromise in the industry.
Q: What is the relationship between JWT and OAuth?
OAuth 2.0 is an authorization framework that defines how clients obtain access permissions (authorization code flow, client credentials flow, etc.). JWT is a token format. They are concepts from different dimensions and can be used together: the Access Token issued by OAuth 2.0 can be in JWT format (e.g., Auth0, Google's implementation) or a random string (e.g., GitHub's Personal Access Token). Simply put: OAuth defines "how to get the token", JWT defines "what the token looks like".
Q: How to choose between HS256 and RS256?
If you have a monolithic application or internal service, choose HS256: simple implementation, high performance, manage one secret.
If your architecture is microservices, or you need third-party services to verify your token (e.g., partner systems), choose RS256: the authentication service holds the private key for signing, and other services only need the public key to verify, without sharing private information, which is more secure. IdPs like Auth0, Google, and Apple use RS256/ES256.
Summary
JWT is essentially a design that "embeds user state into the token"—it sacrifices the ability for "real-time revocation" in exchange for the horizontal scalability brought by stateless verification.
Remember these three core principles:
- Do not put sensitive data in Payload (Base64 is not encryption)
- Short expiration for Access Token + Refresh Token mechanism (balance security and experience)
- Store in HttpOnly cookies on the web (first line of defense against XSS)
Choose JWT in suitable scenarios; for scenarios requiring real-time revocation (e.g., government, finance), consider stateful solutions like Session + Redis—there is no silver bullet, only the right tool.
