Все статьи
Кодирование

JWT токен: структура и как декодировать

JSON Web Token: header, payload, signature. Как работает аутентификация JWT, безопасность, декодирование.

17 сентября 2025
9 мин чтения
ConvertHub
Обновлено 07.09.2026
#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_adQssw5c

Header

Заголовок описывает тип токена и алгоритм подписи. Обычно это объект с двумя полями:

{
  "alg": "HS256",
  "typ": "JWT"
}

alg — алгоритм подписи (HS256, RS256, ES256, none и др.),typ — всегда «JWT».

Payload

Полезная нагрузка содержит утверждения (claims) — пары «ключ-значение» с информацией о пользователе и самом токене. Стандарт определяет несколько зарезервированных claims:

ClaimНазначение
issIssuer — кто выпустил токен
subSubject — о ком токен (обычно ID пользователя)
audAudience — для кого предназначен
expExpiration time — когда истекает (Unix timestamp)
nbfNot before — с какого момента действителен
iatIssued at — когда выпущен
jtiJWT 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

  1. Пользователь отправляет логин и пароль на POST /api/login.
  2. Сервер проверяет учётные данные, формирует payload с информацией о пользователе, подписывает токен секретным ключом и возвращает клиенту.
  3. Клиент сохраняет токен (обычно в localStorage или в httpOnly-куки) и прикладывает к каждому запросу в заголовке Authorization: Bearer <token>.
  4. Сервер получает запрос, извлекает токен, проверяет подпись, проверяет срок действия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.

Попробуйте эти инструменты

Похожие статьи