Как создать подписанный JWT
- Выберите Алгоритм. Значение
algв поле Заголовок (JSON) меняется вслед за выбором автоматически. - Отредактируйте поле Полезная нагрузка (JSON). + iat сейчас вставляет текущее время, + exp через 1 ч задаёт истечение через час; оба значения в секундах, как требует спецификация JWT.
- Для HS256, HS384 или HS512 введите Секрет и выставьте Формат секрета: текст, Base64 или Hex. Для остальных алгоритмов вставьте закрытый ключ или нажмите Сгенерировать пару ключей.
- Нажмите Скопировать токен или Открыть в декодере, чтобы проверить его: откроется вкладка Декодировать и проверить с уже подставленными токеном и ключом для проверки.
При загрузке страницы в примере стоит рабочий токен HS256, подписанный секретом base64hub-demo-secret, так что можно поменять один клейм и посмотреть, как вместе с ним меняется подпись.
Как выбрать алгоритм
| Алгоритм | Тип ключа | Кто может проверить | Подпись в токене |
|---|---|---|---|
| HS256 | Общий секрет (HMAC-SHA256) | Любой, у кого есть тот же секрет | 43 символа |
| RS256, PS256 | Закрытый ключ RSA от 2048 бит | Любой, у кого есть открытый ключ | 342 символа (RSA-2048) |
| ES256 | Закрытый ключ EC на P-256 | Любой, у кого есть открытый ключ | 86 символов |
| EdDSA | Закрытый ключ Ed25519 | Любой, у кого есть открытый ключ | 86 символов |
Если подписывает и проверяет один сервис, проще всего взять HS256, например для токена сессии, который проверяет тот же бэкенд. Как только проверять должна вторая сторона, поделиться с ней секретом HMAC значит дать ей и возможность выпускать токены, поэтому переходите на асимметричный алгоритм и публикуйте только открытый ключ. ES256 и EdDSA дают куда более короткие токены, чем RSA, что важно для cookie и заголовков. PS256 означает RSA со схемой дополнения PSS; выбирайте его, только если его поддерживают все проверяющие стороны.
none есть в списке для тестирования парсеров. У токена с alg: none пустая подпись, и любой сервер, который его примет, может обмануть кто угодно, поэтому инструмент об этом предупреждает.
Какие ключи кодировщик принимает и генерирует
Для подписи подойдёт ключ PEM PKCS#8 (BEGIN PRIVATE KEY), PKCS#1 RSA PRIVATE KEY, SEC1 EC PRIVATE KEY или закрытый JWK. Чтобы сделать тестовые ключи в OpenSSL вместо генератора:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa.pem
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec.pem
openssl genpkey -algorithm ed25519 -out ed.pem
openssl pkey -in ec.pem -pubout -out ec.pub
Сгенерировать пару ключей создаёт в браузере через WebCrypto ключи RSA-2048, P-256, P-384, P-521 или Ed25519, смотря по алгоритму. Вы получаете закрытый ключ в PEM PKCS#8, открытый ключ в PEM SPKI и JWKS, где kid равен отпечатку ключа по RFC 7638. Именно JWKS отдают по адресу jwks_uri, чтобы проверяющие стороны могли его скачать.
Для HS256 секрет используется как байты. Если ваш сервер читает секрет из переменной окружения и сначала декодирует его из Base64, выставьте Формат секрета на Base64 и здесь, иначе подписи не совпадут. Значение из пресета «Секрет JWT HS256» в генераторе ключей Base64 записано в Base64URL и декодируется в 32 байта.
Какие клеймы класть в полезную нагрузку
Полезный access-токен обычно несёт iss (кто выпустил), sub (о ком он), aud (для какого API), iat и короткий exp. Добавьте jti, если нужно отзывать отдельные токены. Время записывается в целых секундах с 1970 года; если вы формируете его в JavaScript, используйте Math.floor(Date.now() / 1000), потому что Date.now() возвращает миллисекунды.
Полезная нагрузка подписывается, но никогда не шифруется. Любой, кто увидит токен, декодирует его декодером JWT, так что не кладите туда пароли, API-ключи и персональные данные, которые вы не стали бы выводить в лог.
Подпись JWT в коде
Формат достаточно прост, чтобы собрать его вручную, и это помогает при отладке библиотеки. Эта функция на Node.js выдаёт ровно тот же токен, что и jwt.encode(payload, secret, algorithm="HS256") из PyJWT на тех же данных:
const crypto = require("crypto");
const b64url = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64url");
function signHS256(payload, secret) {
const input = b64url({ alg: "HS256", typ: "JWT" }) + "." + b64url(payload);
const sig = crypto.createHmac("sha256", secret).update(input).digest("base64url");
return input + "." + sig;
}
console.log(signHS256({ sub: "user-42", iat: 1767225600, exp: 1767229200 },
"replace-me-with-32-random-bytes!"));
Она печатает eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjkyMDB9.F3xnXnXW8nUQy-Ay37DF626Lb5lHPKI3IFvqoEf3mTw. Если ввести на этой странице тот же заголовок, нагрузку и секрет, получится та же строка. Перед подписью инструмент сжимает JSON, так что отступы не важны, а вот порядок ключей важен, потому что подпись покрывает точные байты. В продакшене используйте поддерживаемую библиотеку; про сторону кодирования см. Base64 в Node.js.
Вопросы и ответы
Какой длины должен быть секрет HS256?
Не меньше 256 бит, то есть 32 случайных байта. RFC 7518 требует, чтобы ключ был не короче результата хеш-функции. Короткий секрет, похожий на пароль, можно подобрать офлайн по одному-единственному токену, поэтому сгенерируйте его в генераторе ключей Base64.
Что выбрать: HS256 или RS256?
HS256 подходит, когда токены выпускает и проверяет один и тот же сервис: обе стороны делят один секрет. RS256, ES256 или EdDSA нужны, когда проверять должны другие стороны: они получают только открытый ключ и сами выпускать токены не могут.
Почему мой закрытый ключ выдаёт ошибку?
Ключи, защищённые паролем (BEGIN ENCRYPTED PRIVATE KEY), не принимаются. Снимите пароль командой openssl pkey -in key.pem -out plain.pem и вставьте результат. Кроме того, тип ключа должен подходить к алгоритму: ключом EC нельзя подписать RS256.
Как добавить в токен kid?
После Сгенерировать пару ключей скопируйте kid из вывода JWKS и добавьте его в JSON заголовка, например "kid": "…". По нему проверяющая сторона выбирает нужный ключ из своего набора.