Введение
JWT (JSON Web Token) — это открытый стандарт (RFC 7519) для безопасной передачи информации между сторонами в виде JSON объекта. JWT токены широко используются для аутентификации и авторизации в современных API, особенно в микросервисных архитектурах.
В этом руководстве мы разберем JWT от основ до практической реализации: структуру токена, создание и валидацию, работу с refresh tokens, безопасность и best practices для использования JWT в API.
✅ Что вы узнаете:
- ✅ Что такое JWT и как он работает
- ✅ Структура JWT (header, payload, signature)
- ✅ Создание и валидация JWT токенов
- ✅ Refresh tokens и управление токенами
- ✅ Безопасность JWT и best practices
- ✅ Реализация JWT на разных языках
- ✅ Распространенные ошибки и как их избежать
- ✅ Когда использовать JWT, а когда нет
📋 Содержание
Что такое JWT? 🔐
JWT (JSON Web Token) — это компактный и самодостаточный способ безопасной
передачи информации между сторонами. JWT состоит из трех частей, разделенных точками:
header.payload.signature
✅ Преимущества JWT:
- Статeless — серверу не нужно хранить сессии, вся информация в токене
- Компактность — токен можно передавать в URL, POST параметрах или HTTP заголовке
- Самодостаточность — токен содержит всю необходимую информацию
- Масштабируемость — идеально для микросервисов и распределенных систем
- Кросс-доменность — можно использовать между разными доменами
Как работает JWT?
Процесс работы с JWT:
Пользователь отправляет учетные данные (логин/пароль) на сервер.
Сервер проверяет учетные данные и создает JWT токен с информацией о пользователе.
Сервер отправляет JWT токен клиенту (обычно в ответе на запрос авторизации).
Клиент сохраняет токен и отправляет его в заголовке Authorization: Bearer <token> при каждом запросе к API.
Сервер проверяет подпись токена и извлекает информацию из payload без обращения к базе данных.
Структура JWT 🧩
JWT состоит из трех частей, разделенных точками (.):
Три части JWT токена:
Содержит метаданные о токене: тип токена (JWT) и алгоритм подписи (например, HS256, RS256).
{
"alg": "HS256",
"typ": "JWT"
}
После Base64URL кодирования: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Содержит claims (утверждения) — информацию о пользователе и метаданные токена.
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
После Base64URL кодирования: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Создается путем подписи закодированных header и payload секретным ключом.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Результат: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Стандартные Claims (JWT Claims)
| Claim | Описание | Пример |
|---|---|---|
| iss (issuer) | Кто выдал токен | "https://api.example.com" |
| sub (subject) | Идентификатор пользователя | "1234567890" |
| aud (audience) | Для кого предназначен токен | "my-api" |
| exp (expiration) | Время истечения токена (Unix timestamp) | 1516242622 |
| iat (issued at) | Время создания токена | 1516239022 |
| nbf (not before) | Токен недействителен до этого времени | 1516239022 |
| jti (JWT ID) | Уникальный идентификатор токена | "550e8400-e29b-41d4-a716-446655440000" |
Создание JWT токенов 🔨
Node.js (jsonwebtoken)
// create-jwt.js
const jwt = require('jsonwebtoken');
// Секретный ключ (в продакшене храните в переменных окружения)
const SECRET_KEY = 'your-secret-key';
// Создание JWT токена
function createToken(user) {
const payload = {
sub: user.id,
name: user.name,
email: user.email,
role: user.role,
iat: Math.floor(Date.now() / 1000), // Текущее время
exp: Math.floor(Date.now() / 1000) + (60 * 60) // Истекает через 1 час
};
const token = jwt.sign(payload, SECRET_KEY, {
algorithm: 'HS256',
expiresIn: '1h'
});
return token;
}
// Пример использования
const user = {
id: '1234567890',
name: 'John Doe',
email: 'john@example.com',
role: 'user'
};
const token = createToken(user);
console.log('JWT Token:', token);
// Результат:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0MjYyMn0.xxx
Python (PyJWT)
# create_jwt.py
import jwt
import datetime
SECRET_KEY = 'your-secret-key'
def create_token(user):
payload = {
'sub': user['id'],
'name': user['name'],
'email': user['email'],
'role': user['role'],
'iat': datetime.datetime.utcnow(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return token
# Пример использования
user = {
'id': '1234567890',
'name': 'John Doe',
'email': 'john@example.com',
'role': 'user'
}
token = create_token(user)
print(f'JWT Token: {token}')
PHP (firebase/php-jwt)
// create_jwt.php
<?php
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$secretKey = 'your-secret-key';
function createToken($user) {
global $secretKey;
$payload = [
'sub' => $user['id'],
'name' => $user['name'],
'email' => $user['email'],
'role' => $user['role'],
'iat' => time(),
'exp' => time() + (60 * 60) // 1 час
];
$token = JWT::encode($payload, $secretKey, 'HS256');
return $token;
}
// Пример использования
$user = [
'id' => '1234567890',
'name' => 'John Doe',
'email' => 'john@example.com',
'role' => 'user'
];
$token = createToken($user);
echo "JWT Token: " . $token;
?>
Валидация JWT токенов ✅
Валидация JWT токена включает проверку подписи, срока действия и других claims.
Node.js
// validate-jwt.js
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
const SECRET_KEY = 'your-secret-key';
// Middleware для проверки JWT
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Токен не предоставлен' });
}
jwt.verify(token, SECRET_KEY, (err, decoded) => {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Токен истек' });
}
if (err.name === 'JsonWebTokenError') {
return res.status(401).json({ error: 'Недействительный токен' });
}
return res.status(403).json({ error: 'Ошибка проверки токена' });
}
req.user = decoded; // Добавляем информацию о пользователе в запрос
next();
});
}
// Защищенный endpoint
app.get('/api/user', authenticateToken, (req, res) => {
res.json({
message: 'Доступ разрешен',
user: req.user
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Python (Flask)
# validate_jwt.py
from flask import Flask, request, jsonify
import jwt
from functools import wraps
app = Flask(__name__)
SECRET_KEY = 'your-secret-key'
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = None
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
try:
token = auth_header.split(' ')[1] # Bearer TOKEN
except IndexError:
return jsonify({'error': 'Неверный формат токена'}), 401
if not token:
return jsonify({'error': 'Токен не предоставлен'}), 401
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
request.user = decoded
except jwt.ExpiredSignatureError:
return jsonify({'error': 'Токен истек'}), 401
except jwt.InvalidTokenError:
return jsonify({'error': 'Недействительный токен'}), 401
return f(*args, **kwargs)
return decorated
@app.route('/api/user', methods=['GET'])
@token_required
def get_user():
return jsonify({
'message': 'Доступ разрешен',
'user': request.user
})
if __name__ == '__main__':
app.run(port=5000)
Refresh Tokens 🔄
Refresh Token — это долгоживущий токен, который используется для получения новых access tokens без повторной авторизации пользователя.
✅ Преимущества Refresh Tokens:
- Короткий срок жизни Access Token — 15-60 минут
- Долгий срок жизни Refresh Token — недели или месяцы
- Безопасность — если access token скомпрометирован, ущерб ограничен
- Отзыв доступа — можно отозвать refresh token в базе данных
Реализация Refresh Tokens
// refresh-tokens.js
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const ACCESS_TOKEN_SECRET = 'access-secret-key';
const REFRESH_TOKEN_SECRET = 'refresh-secret-key';
// Создание пары токенов
function createTokenPair(user) {
// Access Token (короткоживущий)
const accessToken = jwt.sign(
{
sub: user.id,
name: user.name,
role: user.role
},
ACCESS_TOKEN_SECRET,
{ expiresIn: '15m' }
);
// Refresh Token (долгоживущий)
const refreshToken = jwt.sign(
{
sub: user.id,
type: 'refresh'
},
REFRESH_TOKEN_SECRET,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
// Обновление access token
function refreshAccessToken(refreshToken) {
try {
// Проверяем refresh token
const decoded = jwt.verify(refreshToken, REFRESH_TOKEN_SECRET);
// Проверяем, что токен не в черном списке (в реальном приложении)
// if (await isTokenBlacklisted(refreshToken)) {
// throw new Error('Токен отозван');
// }
// Создаем новый access token
const user = { id: decoded.sub };
const newAccessToken = jwt.sign(
{
sub: user.id,
name: user.name,
role: user.role
},
ACCESS_TOKEN_SECRET,
{ expiresIn: '15m' }
);
return newAccessToken;
} catch (error) {
throw new Error('Недействительный refresh token');
}
}
// Пример использования
app.post('/api/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'Refresh token не предоставлен' });
}
try {
const newAccessToken = refreshAccessToken(refreshToken);
res.json({ accessToken: newAccessToken });
} catch (error) {
res.status(401).json({ error: error.message });
}
});
Реализация на разных языках 💻
Go (github.com/golang-jwt/jwt)
// jwt.go
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
var secretKey = []byte("your-secret-key")
type Claims struct {
UserID string `json:"sub"`
Name string `json:"name"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func CreateToken(userID, name, role string) (string, error) {
claims := Claims{
UserID: userID,
Name: name,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secretKey)
}
func ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("недействительный токен")
}
Java (io.jsonwebtoken:jjwt)
// JwtUtil.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key-which-must-be-at-least-256-bits";
private static final Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
public static String createToken(String userId, String name, String role) {
return Jwts.builder()
.setSubject(userId)
.claim("name", name)
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 час
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public static Claims validateToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
}
Безопасность JWT 🛡️
Безопасность JWT критически важна. Вот основные рекомендации:
✅ Best Practices безопасности:
- Используйте HTTPS — все коммуникации должны быть зашифрованы
- Короткий срок жизни Access Token — 15-60 минут
- Сильные секретные ключи — минимум 256 бит для HS256
- Проверяйте алгоритм — всегда явно указывайте алгоритм при валидации
- Храните токены безопасно — httpOnly cookies для веб, secure storage для мобильных
- Используйте Refresh Tokens — для долгоживущих сессий
- Валидируйте все claims — exp, nbf, iss, aud
- Не храните чувствительные данные — только необходимый минимум
⚠️ Распространенные уязвимости:
- Algorithm confusion — атака, когда злоумышленник меняет алгоритм на "none"
- Weak secrets — слабые секретные ключи
- Token в URL — токены в URL могут попасть в логи
- XSS атаки — хранение токенов в localStorage
- Отсутствие проверки exp — не проверяется срок действия
Защита от Algorithm Confusion
// Безопасная валидация JWT
const jwt = require('jsonwebtoken');
function validateToken(token) {
// ВАЖНО: Всегда явно указывайте алгоритм
const decoded = jwt.verify(token, SECRET_KEY, {
algorithms: ['HS256'] // Явно указываем алгоритм
});
// Проверяем все необходимые claims
if (!decoded.exp || decoded.exp < Math.floor(Date.now() / 1000)) {
throw new Error('Токен истек');
}
if (!decoded.sub) {
throw new Error('Отсутствует subject');
}
return decoded;
}
// НЕПРАВИЛЬНО - уязвимо к algorithm confusion
function unsafeValidateToken(token) {
// Не указывает алгоритм - уязвимо!
return jwt.verify(token, SECRET_KEY);
}
Где хранить JWT токены?
| Место хранения | Безопасность | Когда использовать |
|---|---|---|
| httpOnly Cookies | ✅ Высокая | Веб-приложения (защита от XSS) |
| Память (JavaScript) | ✅ Высокая | SPA приложения |
| Secure Storage | ✅ Высокая | Мобильные приложения (Keychain/Keystore) |
| localStorage | ❌ Низкая | Не рекомендуется (уязвимо к XSS) |
| sessionStorage | ❌ Низкая | Не рекомендуется (уязвимо к XSS) |
Best Practices 📚
✅ Рекомендации по использованию JWT:
- Используйте JWT для stateless аутентификации — идеально для микросервисов
- Короткий срок жизни Access Token — 15-60 минут
- Используйте Refresh Tokens — для долгоживущих сессий
- Храните минимальные данные — только необходимые claims
- Валидируйте все claims — exp, nbf, iss, aud
- Используйте сильные алгоритмы — HS256 или RS256
- Логируйте события — создание, валидация, ошибки
- Реализуйте blacklist — для отзыва токенов при необходимости
Когда использовать JWT?
✅ JWT подходит для:
- Микросервисные архитектуры
- Stateless API
- Распределенные системы
- Мобильные приложения
- SPA (Single Page Applications)
- Когда нужна масштабируемость
❌ JWT НЕ подходит для:
- Когда нужен немедленный отзыв токенов
- Очень большие payload (JWT должен быть компактным)
- Когда нужна полная анонимность
- Критически важные системы без дополнительных мер безопасности
Заключение
JWT токены — это мощный инструмент для аутентификации в современных API. При правильной реализации они обеспечивают безопасность, масштабируемость и удобство использования.
💡 Ключевые выводы:
- JWT состоит из трех частей: header, payload, signature
- JWT идеален для stateless аутентификации в микросервисах
- Используйте короткий срок жизни access tokens и refresh tokens
- Всегда валидируйте алгоритм и все claims
- Храните токены безопасно (httpOnly cookies, secure storage)
- Используйте HTTPS для всех коммуникаций
- Не храните чувствительные данные в JWT
Создайте Mock API с JWT аутентификацией за 2 минуты
Хотите протестировать JWT интеграцию? Создайте Mock API с помощью LightBox API и попробуйте различные сценарии аутентификации без необходимости настраивать сложный backend.
Попробовать бесплатно →