JWT и Base64
JWT (JSON Web Token) — самый популярный формат для авторизации в современных API. RFC 7519, разработан в 2015. Состоит из трёх частей через точку: header.payload.signature. Каждая часть — это JSON, закодированный в специальном варианте Base64 — Base64URL.
Пример JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTEyMyIsIm5hbWUiOiJJdmFuIiwiZXhwIjoxNzM1Njg5NjAwfQ.HpAFxOHr3l5ZcZD9yLzxs1F7Q8Vk0zUBzKqDZE6KQ-w
Видны три части через точку. Декодируем каждую через Base64:
Header = {"alg":"HS256","typ":"JWT"}
Payload = {"sub":"user-123","name":"Ivan","exp":1735689600}
Signature = HMAC-SHA256(header.payload, secret_key)Base64URL vs обычный Base64
Обычный Base64 использует 64 символа: A-Z, a-z, 0-9, +, /. Знак + и / небезопасны в URL — их нужно URL-encoding (%2B, %2F). Это удваивает длину и усложняет работу. Base64URL решает проблему:
Чтобы декодировать Base64URL обычным Base64-декодером:
- Заменить
-на+. - Заменить
_на/. - Добавить padding
=до длины кратной 4.
JWT uses the URL-safe variant of Base64 (Base64URL). The padding character '=' is removed. The header and payload are JSON objects that are Base64URL-encoded, then concatenated with a period separator.— RFC 7519 — JSON Web Token (JWT), section 3.1
3 части токена
Header
Описывает тип токена и алгоритм подписи:
{
"alg": "HS256", // HMAC-SHA-256
"typ": "JWT", // тип токена
"kid": "key-1" // key ID (опционально, для key rotation)
}Алгоритмы:
- HS256, HS384, HS512 — HMAC + SHA-2. Симметричный ключ.
- RS256, RS384, RS512 — RSA + SHA-2. Асимметричный (publickey.privatekey).
- ES256, ES384, ES512 — ECDSA + SHA-2. Асимметричный, более компактный.
- PS256 — RSA-PSS + SHA-256. Современная замена RS256.
- none — без подписи. НИКОГДА не используйте в production.
Payload (claims)
{
// Стандартные claims (RFC 7519)
"iss": "https://api.example.com", // issuer
"sub": "user-123", // subject (user ID)
"aud": "mobile-app", // audience
"exp": 1735689600, // expiration (Unix timestamp)
"iat": 1735603200, // issued at
"nbf": 1735603200, // not before
"jti": "abc-123-def-456", // JWT ID
// Кастомные claims
"name": "Иван Петров",
"email": "ivan@example.com",
"roles": ["admin", "user"],
"permissions": ["read:users", "write:orders"]
}Payload — ОТКРЫТЫЙ текст (после Base64URL декодирования). Не храните в нём пароли, секретные ключи, токены доступа к третьим API.
Signature
Подпись = функция от header + payload + secret key. Обеспечивает целостность токена. Если кто-то изменит payload, signature не совпадёт.
// HS256 (HMAC-SHA256) signature = HMAC-SHA256( base64url(header) + '.' + base64url(payload), secret_key ); // RS256 (RSA-SHA256) signature = RSA_SIGN( SHA256(base64url(header) + '.' + base64url(payload)), private_key );
Декодирование в коде
JavaScript (без проверки подписи)
function decodeJwt(token) {
const [headerB64, payloadB64, signatureB64] = token.split('.');
// Base64URL → Base64
const toBase64 = (s) => s.replace(/-/g, '+').replace(/_/g, '/');
return {
header: JSON.parse(atob(toBase64(headerB64))),
payload: JSON.parse(atob(toBase64(payloadB64))),
signature: signatureB64,
};
}
// Использование
const decoded = decodeJwt(token);
console.log(decoded.payload.sub); // user-123
console.log(new Date(decoded.payload.exp * 1000)); // expiration date
// Проверка истечения
const now = Math.floor(Date.now() / 1000);
if (decoded.payload.exp < now) {
console.log('Token expired!');
}JavaScript (с проверкой подписи через jose)
import { jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
try {
const { payload } = await jwtVerify(token, secret, {
issuer: 'https://api.example.com',
audience: 'mobile-app',
});
console.log('Valid token:', payload);
} catch (e) {
console.log('Invalid token:', e.message);
}Python
import jwt
import base64
import json
# Без проверки подписи (только декодирование)
def decode_jwt_unsafe(token):
header_b64, payload_b64, signature_b64 = token.split('.')
# Add padding
payload_b64 += '=' * (4 - len(payload_b64) % 4)
payload_bytes = base64.urlsafe_b64decode(payload_b64)
return json.loads(payload_bytes)
# С проверкой подписи (PyJWT)
def decode_jwt_safe(token, secret):
return jwt.decode(token, secret, algorithms=["HS256"])
# Использование
payload = decode_jwt_unsafe(token) # для отладки
payload = decode_jwt_safe(token, secret) # для productionБезопасность
- Не храните секреты в payload. Любой может декодировать. Только идентификаторы и метаданные.
- Короткий exp. Access token — 15-30 минут. Refresh token — 7-30 дней. Балансирует UX и безопасность.
- Проверяйте exp на сервере. Хотя клиент должен проверять для UX, сервер ОБЯЗАН проверять.
- HTTPS обязателен. JWT в HTTP — токен виден в логах прокси, истории браузера. Только HTTPS.
- Не разрешайте none алгоритм. Жёстко задайте список allowed algorithms в verify.
- Не используйте JWT для сессий с возможностью отзыва. Refresh token + блэклист или опаковский session ID.
- HTTPOnly cookie для refresh. Защита от XSS. Access token может быть в memory.
- RFC 7519 — JSON Web Token (JWT). IETF. datatracker.ietf.org/doc/html/rfc7519. 2015.
- RFC 7515 — JSON Web Signature (JWS). IETF. datatracker.ietf.org/doc/html/rfc7515. 2015.
- JWT Best Practices — OWASP. OWASP. owasp.org/www-chapter-vancouver/assets/presentations/2020-01_Attacking_and_Securing_JWT.pdf. 2020.
