JWT токен: структура и как декодировать
JSON Web Token: header, payload, signature. Как работает аутентификация JWT, безопасность, декодирование.
Посчитайте прямо здесь: JWT Token → Decoded JWT
Открыть инструмент целиком →Введение
JSON Web Token, или JWT — это открытый стандарт (RFC 7519) для безопасной передачи утверждений (claims) между сторонами в виде компактной строки. JWT чаще всего применяется для аутентификации: сервер выдаёт токен после логина, а клиент прикладывает его к каждому следующему запросу. В этой статье разберём структуру JWT, как он работает, чем отличается от классических сессий, какие у него уязвимости и как декодировать токен вручную.
Чтобы быстро разобрать токен на части, используйте наш JWT декодер — он покажет header, payload и подпись прямо в браузере.
Что такое JWT и зачем он нужен
Классическая сессионная аутентификация работает так: после входа сервер создаёт запись в хранилище (например, в Redis), выдаёт клиенту идентификатор сессии в куки, а при каждом запросе ищет этот идентификатор и восстанавливает контекст пользователя. Это просто, но требует общего хранилища сессий на всех серверах, что усложняет масштабирование.
JWT решает задачу иначе: сервер подписывает токен и отдаёт клиенту, а сам ничего не хранит. При следующем запросе клиент присылает токен, сервер проверяет подпись и, если она валидна, доверяет содержимому. Это называется stateless-аутентификацией— серверу не нужно держать состояние сессии.
Главное свойство JWT: токен подписан, но не зашифрован. Любой, кто перехватит токен, сможет прочитать его содержимое. Поэтому в payload никогда нельзя класть пароли, секреты и другие чувствительные данные. JWT гарантирует лишь целостность — что токен не был изменён после выпуска.
Структура JWT
JWT состоит из трёх частей, разделённых точками:
xxxxx.yyyyy.zzzzz
│ │ │
│ │ └── signature (подпись)
│ └───────── payload (полезная нагрузка)
└───────────────── header (заголовок)Каждая часть — это JSON, сериализованный и закодированный в Base64url. Пример реального токена:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cHeader
Заголовок описывает тип токена и алгоритм подписи. Обычно это объект с двумя полями:
{
"alg": "HS256",
"typ": "JWT"
}alg — алгоритм подписи (HS256, RS256, ES256, none и др.),typ — всегда «JWT».
Payload
Полезная нагрузка содержит утверждения (claims) — пары «ключ-значение» с информацией о пользователе и самом токене. Стандарт определяет несколько зарезервированных claims:
| Claim | Назначение |
|---|---|
iss | Issuer — кто выпустил токен |
sub | Subject — о ком токен (обычно ID пользователя) |
aud | Audience — для кого предназначен |
exp | Expiration time — когда истекает (Unix timestamp) |
nbf | Not before — с какого момента действителен |
iat | Issued at — когда выпущен |
jti | JWT ID — уникальный идентификатор токена |
Кроме зарезервированных, можно добавлять произвольные claims: name,email, role, permissions и так далее.
{
"sub": "1234567890",
"name": "Иван Петров",
"email": "ivan@example.com",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}Signature
Подпись гарантирует, что токен не был изменён. Для её вычисления берут закодированный header, закодированный payload, секретный ключ и применяют алгоритм, указанный в header:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)Если хотя бы один символ в header или payload изменить, подпись перестанет совпадать, и сервер отклонит токен. Подробнее о HMAC — в нашей статье об аутентификации сообщений.
Как работает аутентификация с JWT
- Пользователь отправляет логин и пароль на
POST /api/login. - Сервер проверяет учётные данные, формирует payload с информацией о пользователе, подписывает токен секретным ключом и возвращает клиенту.
- Клиент сохраняет токен (обычно в localStorage или в httpOnly-куки) и прикладывает к каждому запросу в заголовке
Authorization: Bearer <token>. - Сервер получает запрос, извлекает токен, проверяет подпись, проверяет срок действия
expи другие claims. Если всё в порядке — обрабатывает запрос от имени пользователя.
Алгоритмы подписи
| Алгоритм | Тип | Описание |
|---|---|---|
| HS256 | Симметричный | HMAC с SHA-256, общий секрет |
| HS384 | Симметричный | HMAC с SHA-384 |
| HS512 | Симметричный | HMAC с SHA-512 |
| RS256 | Асимметричный | RSA с SHA-256, приватный ключ подписывает |
| RS384 / RS512 | Асимметричный | RSA с SHA-384 / SHA-512 |
| ES256 | Асимметричный | ECDSA с P-256 и SHA-256 |
| PS256 | Асимметричный | RSA-PSS с SHA-256 |
| none | — | Без подписи. Опасно, использовать нельзя |
Симметричные алгоритмы (HS*) удобны для монолитных приложений: один секрет известен только серверу. Асимметричные (RS*, ES*) подходят для микросервисов: приватный ключ есть только у сервиса аутентификации, а любой микросервис может проверить подпись публичным ключом.
Декодируем JWT вручную
Поскольку первые две части токена — это просто Base64url-кодированный JSON, их можно раскодировать без какого-либо секрета. Это иногда смущает новичков: «Если токен можно прочитать, как же он безопасен?». Безопасность JWT не в тайне содержимого, а в невозможности его изменить без знания ключа.
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const [headerB64, payloadB64, signatureB64] = token.split('.');
// Base64url нужно привести к обычному Base64
function base64UrlDecode(str) {
str = str.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
return atob(str);
}
const header = JSON.parse(base64UrlDecode(headerB64));
const payload = JSON.parse(base64UrlDecode(payloadB64));
console.log(header);
// { alg: "HS256", typ: "JWT" }
console.log(payload);
// { sub: "1234567890", name: "John Doe", iat: 1516239022 }Если хотите быстро проверить токен без написания кода — используйте наш JWT декодер.
Где хранить JWT на клиенте
Это один из самых обсуждаемых вопросов. Основные варианты:
1. localStorage
Просто и удобно, но любой JavaScript на странице (включая вставленный через XSS) имеет доступ к localStorage. Если в приложении есть XSS-уязвимость, атакующий легко украдёт токен. Подробнее о защите от XSS — в статье об HTML сущностях.
2. httpOnly cookie
Браузер автоматически отправляет куки с каждым запросом, а JavaScript не имеет к ним доступа. Это защищает от кражи через XSS. Но появляется другая уязвимость — CSRF: сайт злоумышленника может отправить запрос на ваш API, и кука приложится автоматически. Защита — анти-CSRF токены или заголовок SameSite.
3. sessionStorage
Аналог localStorage, но очищается при закрытии вкладки. Подходит для коротких сессий.
4. In-memory
Токен хранится только в переменной JavaScript. Самый безопасный вариант от XSS, но требует повторного входа при каждой перезагрузке. Используется в связке с refresh-токеном в httpOnly cookie.
Refresh-токены
Если access-токен живёт долго, его компрометация — большая проблема. Поэтому принято использовать два токена:
- Access token — короткоживущий (5–15 минут), прикладывается к каждому запросу.
- Refresh token — долгоживущий (дни или недели), используется только для получения нового access-токена. Хранится в httpOnly cookie.
Когда access-токен истекает, клиент обращается к POST /api/refresh с refresh-токеном и получает новый access-токен. Если refresh-токен скомпрометирован, сервер может внести его в чёрный список.
Уязвимости и типичные ошибки
1. alg: none
Стандарт JWT допускает алгоритм «none» — токен без подписи. Исторически многие библиотеки принимали такие токены по умолчанию, что позволяло атакующему подменить содержимое. Современные библиотеки либо запрещают «none», либо требуют явного указания. Никогда не принимайте токены с alg: none в production.
2. Подмена алгоритма HS256 → RS256
Если сервер использует RS256 (асимметричный), атакующий может попытаться подменитьalg на HS256 и подписать токен публичным ключом сервера как HMAC-секретом. Если библиотека слепо верит полю alg из header, проверка пройдёт. Решение: всегда явно указывать ожидаемый алгоритм при проверке.
3. Слабый секрет
Для HS256 секрет должен быть длинным и случайным. Если используется что-то вроде «secret» или «password», атакующий может подобрать его перебором или по словарю за разумное время. Используйте генераторы криптостойких случайных строк.
4. Долгоживущие access-токены
Если access-токен живёт неделю, его кража даст атакующему неделю доступа. Установите срок жизни access-токена в 5–15 минут и используйте refresh-токены.
5. Игнорирование exp
Библиотеки обычно проверяют exp автоматически, но только если вы явно включите эту проверку. Убедитесь, что просроченные токены отклоняются.
6. Чувствительные данные в payload
Как уже говорилось, payload читается любым, у кого есть токен. Никогда не кладите туда пароли, секреты, номера кредитных карт.
JWT в разных языках
JavaScript (Node.js, jsonwebtoken)
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET;
// Выпуск токена
const token = jwt.sign(
{ sub: '123', name: 'Иван', role: 'admin' },
SECRET,
{ expiresIn: '15m', algorithm: 'HS256' }
);
// Проверка токена
try {
const payload = jwt.verify(token, SECRET, { algorithms: ['HS256'] });
console.log(payload);
} catch (err) {
// Токен недействителен или истёк
}Python (PyJWT)
import jwt
import os
SECRET = os.environ['JWT_SECRET']
# Выпуск
token = jwt.encode(
{'sub': '123', 'name': 'Иван', 'role': 'admin'},
SECRET,
algorithm='HS256',
expires_in=900 # 15 минут
)
# Проверка
try:
payload = jwt.decode(token, SECRET, algorithms=['HS256'])
except jwt.ExpiredSignatureError:
pass # токен истёк
except jwt.InvalidTokenError:
pass # токен недействителенКогда не стоит использовать JWT
JWT — не серебряная пуля. В некоторых сценариях классические сессии подходят лучше:
- Нужно немедленно отозвать сессию. В stateless-схеме сервер не может «убить» токен до истечения срока. Приходится вести чёрный список, что сводит на нет преимущество stateless.
- Один пользователь — много устройств с общим выходом. С сессиями в Redis это тривиально, с JWT — боль.
- Часто меняются права пользователя. В JWT права «зашиты» в токен до истечения. Любые изменения требуют перевыпуска.
- Размер токена критичен. JWT-токен с большим payload может весить несколько килобайт, что увеличивает каждый запрос.
Заключение
JWT — мощный и широко поддерживаемый стандарт для аутентификации в распределённых системах. Он особенно удобен в микросервисной архитектуре и при разработке SPA. Но за удобство приходится платить: token нельзя отозвать до истечения, его содержимое читается любым, а ошибки в настройке библиотек могут привести к критическим уязвимостям.
Соблюдайте простые правила: короткоживущие access-токены, refresh-токены в httpOnly cookie, длинные случайные секреты, явное указание алгоритма при проверке, никаких чувствительных данных в payload. И не забывайте декодировать токены для отладки — это можно сделать в нашем JWT декодере. А если хотите лучше понять, что такое Base64url и как он связан с обычным Base64, читайте статью о Base64.
Попробуйте эти инструменты
Похожие статьи
Base64 — что это и как работает
Принцип кодирования Base64, алфавит, padding, использование в Data URI, email, API. Примеры кодирования.
URL кодирование: percent-encoding explained
Что такое URL encoding, зарезервированные символы, как кодировать/декодировать URL, частые ошибки.
HTML сущности и кодирование спецсимволов
HTML entities, named vs numeric, XSS защита, кодирование кавычек, амперсандов, угловых скобок.
UTF-8 и BOM: что это такое и чем отличается от UTF-16
UTF-8 — что это такое простыми словами: как кодируется кириллица, зачем нужен BOM, чем UTF-8 отличается от UTF-16 и откуда берутся кракозябры.