JWT Tokens Explained: Structure, Security & Decoding

Understand JSON Web Tokens from the inside out. Learn the header, payload, and signature structure, common claims, signing algorithms, and security best practices.

Команда JSONTechFebruary 1, 202510 min read

Что такое JWT?

JSON Web Token (JWT, произносится как «джот») — это компактная, безопасная для URL строка, которая представляет утверждения (claims) между двумя сторонами. Это самый распространённый способ организации аутентификации и авторизации в современных веб-приложениях.

Когда вы входите в веб-приложение и сервер возвращает токен вместо создания сессии на стороне сервера, с большой вероятностью этот токен — JWT. Браузер отправляет его с каждым последующим запросом, а сервер проверяет подпись без обращения к базе данных.

Где используют JWT

  • Аутентификация. После входа пользователя сервер выдаёт JWT. Клиент сохраняет его и отправляет в заголовке Authorization при каждом запросе.
  • Единый вход (SSO). JWT позволяют пользователю один раз пройти аутентификацию и получить доступ к нескольким сервисам. Поставщики идентичности, такие как Auth0, Okta и Keycloak, выдают JWT, которые нижестоящие сервисы могут проверять независимо.
  • Авторизация API. Микросервисы передают друг другу JWT, чтобы подтвердить, что запрос выполнен от имени аутентифицированного пользователя с определёнными правами.
  • Обмен информацией. Поскольку JWT подписаны, они могут передавать доверенные данные между сторонами без дополнительного шага проверки.

Три части JWT

Каждый JWT состоит из трёх частей в кодировке Base64URL, разделённых точками:

header.payload.signature

Вот реальный JWT (сокращён для читаемости):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Разберём каждую часть.

Часть 1: заголовок

Заголовок описывает тип токена и алгоритм подписи. После декодирования из Base64URL:

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

alg указывает проверяющей стороне, какой алгоритм использовать. typ подтверждает, что это JWT.

Часть 2: полезная нагрузка (claims)

Полезная нагрузка содержит claims — фактические данные, которые несёт токен. После декодирования:

{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }

Claims — это пары «ключ — значение». Часть из них стандартизирована («зарегистрированные claims»), можно добавлять и свои.

Полезная нагрузка кодируется в Base64URL, но не шифруется. Любой, у кого есть токен, может декодировать и прочитать payload. Никогда не помещайте в payload JWT секреты, пароли или чувствительные персональные данные.

Часть 3: подпись

Подпись вычисляется из закодированного заголовка, точки, закодированной полезной нагрузки и секретного ключа:

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

Подпись гарантирует, что токен не был изменён.

Попробуйте сами: вставьте любой JWT в наш JWT Decoder, чтобы мгновенно просмотреть все три части.

Распространённые JWT claims

ClaimПолное имяОписание
subSubjectО ком токен (обычно идентификатор пользователя)
issIssuerКто выдал токен (например, URL вашего сервера аутентификации)
audAudienceПредполагаемый получатель (например, URL вашего API)
expExpiration TimeUnix-время, после которого токен недействителен
iatIssued AtUnix-время создания токена
nbfNot BeforeТокен недействителен до этого Unix-времени
jtiJWT IDУникальный идентификатор для предотвращения повторного использования токена

Пользовательские claims — всё остальное, что вы добавляете. Держите их минимальным набором.

Сравнение алгоритмов подписи

АлгоритмТипКлючКогда подходит
HS256Симметричный (HMAC)Один общий секретПростые схемы, где издатель и проверяющая сторона — один и тот же сервис
RS256Асимметричный (RSA)Закрытый ключ подписывает, открытый проверяетРаспределённые системы, SSO
ES256Асимметричный (ECDSA)Закрытый ключ подписывает, открытый проверяетТо же, что RS256, но ключи меньше и быстрее

Симметричный (HS256) проще: обе стороны знают один секрет. Асимметричный (RS256, ES256) лучше для распределённой архитектуры.

Рекомендации по безопасности

  • Не храните секреты в payload. Если нужны зашифрованные токены, используйте JWE.
  • Короткое время жизни. 5–15 минут для access-токенов. Для длинных сессий используйте refresh-токены.
  • Всегда HTTPS. JWT, переданные по HTTP, могут быть перехвачены.
  • Проверяйте все claims. Всегда проверяйте exp, iss и aud.
  • Не принимайте alg: none. Явно отклоняйте этот алгоритм.
  • Используйте allowlist алгоритмов. Принимайте только те алгоритмы, которые вы ожидаете.
  • Храните токены безопасно. Куки с флагом HttpOnly безопаснее, чем localStorage.

Типичные ошибки при работе с JWT

  • Передача JWT в URL. Используйте заголовок Authorization или куки.
  • Полное отсутствие проверки подписи. Злоумышленник может подделать любой токен.
  • Слабый секрет для HS256. Используйте криптографически стойкую случайную строку длиной не менее 256 бит.
  • Токены без срока действия. Отозвать их без смены ключа подписи нельзя.
  • Слишком большой объём данных. Токен на 4 КБ добавляет накладные расходы на каждый вызов API.

Схема обновления токена

  1. Пользователь входит в систему. Сервер возвращает access-токен (короткоживущий, например 15 минут) и refresh-токен (долгоживущий, например 7 дней).
  2. Клиент отправляет access-токен с каждым запросом к API в заголовке Authorization: Bearer.
  3. Когда access-токен истекает, API возвращает 401 Unauthorized.
  4. Клиент отправляет refresh-токен на эндпоинт /refresh.
  5. Сервер проверяет токен и выдаёт новый access-токен (и при необходимости новый refresh-токен — ротация refresh-токена).
  6. Клиент повторяет запрос с новым access-токеном.
// Simplified refresh flow (client-side)
const handleApiRequest = async (url, options) => {
  let response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      Authorization: `Bearer ${getAccessToken()}`,
    },
  });
  if (response.status === 401) {
    const refreshResponse = await fetch("/api/refresh", {
      method: "POST",
      body: JSON.stringify({ refresh_token: getRefreshToken() }),
    });
    if (refreshResponse.ok) {
      const { access_token, refresh_token } = await refreshResponse.json();
      saveTokens(access_token, refresh_token);
      response = await fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          Authorization: `Bearer ${access_token}`,
        },
      });
    } else {
      redirectToLogin();
    }
  }
  return response;
};

JWT и сессии: когда что использовать

АспектJWTСессии на сервере
Хранение состоянияКлиент (сервер без состояния)Сервер (хранилище сессий / БД)
МасштабированиеПроще — нет общего состояния между серверамиНужно общее хранилище сессий (Redis, БД)
ОтзывСложно — нужен блоклист или короткий срок жизниПроще — удалить сессию из хранилища
Размер payloadБольше (claims в каждом запросе)Меньше (только идентификатор сессии в куке)
Кросс-доменУдобно (Bearer-токен)Нужна настройка CORS для кук
МикросервисыУдобно — каждый сервис проверяет самКаждый сервис должен обращаться к хранилищу сессий
ПростотаБольше частей (подпись, обновление токенов)Проще реализовать корректно

Используйте JWT для stateless-аутентификации в распределённых сервисах, мобильных приложениях или для сторонних потребителей API. Используйте сессии для простого отзыва, единого монолитного приложения или самого простого безопасного варианта.

Попробуйте сами: декодируйте и разберите любой JWT мгновенно с помощью нашего JWT Decoder — данные не покидают ваш браузер.

Похожие инструменты