blog.dopana

Back

JWT (JSON Web Token) là chuẩn token compact dùng cho authentication. Self-contained — mọi thông tin đều trong token, không cần session store.

Cấu Trúc JWT#

JWT gồm 3 phần, cách nhau bởi dấu chấm:

header.payload.signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
plaintext
{
  "alg": "HS256",    // Thuật toán sign
  "typ": "JWT"       // Type
}
json

Payload (Claims)#

{
  "sub": "user_123",         // Subject — user ID
  "name": "Alice",
  "role": "admin",
  "iat": 1516239022,         // Issued at
  "exp": 1516242622,         // Expiration
  "iss": "myapp.com",       // Issuer
  "aud": "api.myapp.com"    // Audience
}
json

Signature#

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)
plaintext

Signing Algorithms#

HS256 — Symmetric (HMAC + SHA256)#

import jwt from 'jsonwebtoken';

const secret = process.env.JWT_SECRET!; // Shared secret

const token = jwt.sign(
  { userId: 123, role: 'admin' },
  secret,
  { expiresIn: '15m' }
);

const decoded = jwt.verify(token, secret);
typescript

Ưu: Nhanh, đơn giản. Nhược: Secret phải share giữa các service — nếu leak, tất cả đều ảnh hưởng.

RS256 — Asymmetric (RSA + SHA256)#

import jwt from 'jsonwebtoken';
import fs from 'fs';

const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');

// Sign với private key (chỉ auth server làm)
const token = jwt.sign(
  { userId: 123 },
  privateKey,
  { algorithm: 'RS256', expiresIn: '15m' }
);

// Verify với public key (bất kỳ service nào)
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
typescript

Ưu: Public key ai cũng có, private key chỉ auth server biết. Nhược: Chậm hơn HMAC, key pair management.

ES256 — Elliptic Curve (ECDSA)#

# Tạo key pair
openssl ecparam -genkey -name prime256v1 -noout -out private.pem
openssl ec -in private.pem -pubout -out public.pem
bash

Ưu: Chữ ký nhỏ hơn RSA, nhanh hơn, bảo mật tương đương.

Refresh Token Rotation#

JWT Blacklist#

Khi user logout — access token vẫn còn hạn. Dùng blacklist (Redis):

// Set cookie
res.cookie('access_token', token, {
  httpOnly: true,     // Chống XSS
  secure: true,       // Chỉ HTTPS
  sameSite: 'strict', // Chống CSRF
  maxAge: 15 * 60 * 1000, // 15 phút
  path: '/',
});

// Middleware đọc từ cookie
function authenticate(req: Request, res: Response, next: NextFunction) {
  const token = req.cookies.access_token;
  if (!token) return res.status(401).json({ error: 'Not authenticated' });
  // verify...
}
typescript

JWT với JWKS (JSON Web Key Set)#

Dùng cho OAuth2/OIDC — public keys được serve qua endpoint:

JWT Payload Size#

JWT không nên chứa quá nhiều dữ liệu — mỗi request đều gửi token:

Security Checklist#

  • Access token ngắn (15 phút)
  • Refresh token rotation
  • JWT trong httpOnly cookie (cho web) hoặc Authorization header (cho mobile)
  • aud claim — kiểm tra audience
  • iss claim — kiểm tra issuer
  • Blacklist cho logout
  • Key rotation (RS256/ES256) — đổi key định kỳ
  • Không lưu secret trong code
  • Không decode token mà không verify

Kết Luận#

JWT là chuẩn authentication phổ biến vì:

  • Stateless — không cần session store
  • Portable — mobile, web, service-to-service
  • Self-contained — user info trong token

Nhưng nhớ: JWT không thể revoke trước hạn (trừ khi dùng blacklist). Access token ngắn + refresh token rotation là giải pháp.

Tài liệu tham khảo#