На этой странице

Почему не стоит вставлять JWT в jwt.io (и что использовать вместо этого)

Продакшн-токен оказался у вас в терминале. Нужно срочно понять, кто его выпустил, когда он истекает и какие в нём клеймы. Поэтому вы делаете то же, что и большинство разработчиков: открываете jwt.io, вставляете токен и читаете расшифрованный payload.

Стоп. Этот JWT — учётные данные. Сайт, в который вы только что его вставили, управляется сторонней компанией, в его истории есть тихо добавленная аналитика и продолжающаяся переработка, инициированная самими мейнтейнерами из-за вопросов приватности. Есть лучший ответ, и поисковый запрос, ради которого вы, скорее всего, сюда пришли — «альтернатива jwt.io приватность» — действительно имеет реальное решение.

В этом руководстве разбирается реальная модель угроз, что команда самого jwt.io признала публично, и как убедиться, что отладчик JWT никогда никуда не отправляет ваш токен. Если вы предпочитаете пропустить объяснения и расшифровать токен прямо сейчас в браузере, JWT Decoder полностью клиентский — откройте его, отключитесь от интернета и убедитесь, что он по-прежнему работает.

Кратко: безопасен ли jwt.io?

Короткий ответ: главная страница jwt.io сама вас предупреждает. Их собственный текст предупреждения по сути говорит: «JWT — это учётные данные, будьте осторожны с тем, куда их вставляете». Это предупреждение справедливо, и оно относится к jwt.io в той же мере, что и к любому другому онлайн-инструменту.

Для несекретных демо-токенов jwt.io подходит. Для всего, что относится к реальной среде — продакшн, стейджинг, песочница, интеграция с партнёрами — выбирайте более безопасный вариант по умолчанию: декодируйте локально. Остальная часть статьи — это почему и как.

Что на самом деле происходит, когда вы вставляете JWT в онлайн-декодер

JWT (JSON Web Token) — это три сегмента в кодировке Base64URL, соединённые точками: заголовок, payload и подпись. Заголовок и payload не шифруются, они лишь закодированы. Любой, у кого есть токен, может прочитать его содержимое — включая любой сервис, который его получает, даже если только для того, чтобы «декодировать на клиенте».

Между нажатием Cmd-V и появлением расшифрованных клеймов может произойти несколько вещей:

  • История браузера. Многие инструменты исторически передавали токен в строке запроса URL, а это значит, что он оказывается в истории браузера, в Referer родительских вкладок и в любых синхронизированных хранилищах истории (Chrome Sync, Firefox Sync, профили Edge).
  • Логи сервера. Даже страницы, которые декодируют на JavaScript, всё равно делают запросы за аналитикой, шрифтами и рекламными скриптами. Если токен в URL, он появляется в заголовках Referer, отправляемых этим третьим сторонам.
  • Telemetry SDK. До сентября 2019 года jwt.io отправлял метрики со страницы, несмотря на заявления о работе только на клиенте, что задокументировал инженер Джейми Танна (Capital One Open Banking). «Клиентский» — это утверждение, а не аудит.
  • Будущие изменения кода. Сайт, который сегодня клиентский, завтра может выкатить серверное изменение. Как метко сформулировал один из часто цитируемых комментаторов на Hacker News: «Если вы привыкнете пользоваться такими инструментами, один из них рано или поздно будет скомпрометирован. Через технический взлом, финансовое давление, покупку безнравственной компанией или недовольного сотрудника где-то по пути».

Для этого не нужно никакого злого умысла. Достаточно того, что у кого-то, где-то между вашей клавиатурой и вашими глазами, окажется копия вашего токена на достаточный срок, чтобы им злоупотребить.

Реальная модель угроз

Первое возражение всегда одно и то же: «Но токен ведь истекает через 15 минут — что самое страшное может случиться?» На самом деле — несколько вещей.

Replay внутри окна действия. Пятнадцать минут — это более чем достаточное время, чтобы скрипт, мониторящий аналитический пайплайн, утёкший лог-файл или заголовок Referer, перехватил токен и обратился к вашему API от вашего имени. Если это refresh-токен (а он может быть валиден дни или недели), окно становится гораздо больше.

Клеймы — это карта сокровищ. Типичный payload включает идентификатор пользователя (sub), идентификатор тенанта, список ролей, аудиторию (aud), эмитента (iss) и часто кастомные клеймы вроде email, внутренних флагов или прав доступа к функциям. Даже истёкший JWT точно говорит атакующему, на какого пользователя нацелиться, к какому API обращаться и как выглядит эскалация привилегий в вашей компании.

Не-продовые токены утекают секреты, формой повторяющие продакшн. JWT-песочницы Open Banking и токены партнёрских интеграций часто содержат отпечатки сертификатов, идентификаторы ключей и URL аудиторий, которые чисто отображаются в продакшн-инфраструктуру. Джейми Танна описывал, как «обжигался несколько раз» из-за коллег, вставлявших не-продовые JWT и сертификаты Open Banking-песочниц в jwt.io.

Последствия для compliance не зависят от фактического ущерба. Если ваша компания подпадает под SOC 2, ISO 27001, PCI-DSS или любые правила, родственные HIPAA, вставка учётных данных в стороннее сервис — это инцидент, подлежащий отчётности, независимо от того, было ли что-то реально использовано. Сама находка аудитора и есть инцидент.

Баги в обработке JWT всё ещё выходят. CVE-2026-29000 — обход аутентификации в JwtAuthenticator pac4j — был раскрыт в начале этого года. Баги валидации токенов — это не проблема 2018-го; отладка JWT — это именно тот момент, когда разработчики тянутся к онлайн-инструментам, и именно тот момент, когда утёкшие токены наиболее полезны для атакующего, отслеживающего изменения в той же библиотеке.

Что говорили сами мейнтейнеры jwt.io

Самый сильный аргумент против вставки токенов в jwt.io исходит от самого jwt.io.

В GitHub-issue #700, «The Future of JWT.io», открытом 19 июля 2024 года, инженер DevRel из Okta/Auth0 написал — дословно — что «передача токена на сайт через query-параметры URL вызвала опасения относительно приватности токена». В тикете изложен план переработки, который переключает передачу с query-параметров URL на фрагменты URL, разделяет decode/encode/verify на отдельные виджеты и отключает автоматическую загрузку токенов из URL при загрузке страницы.

Это команда, которая управляет jwt.io, публично обязуется переделать сайт, потому что текущая обработка недостаточно приватна. На момент написания этой статьи часть переработки уже выкачена, а часть нет. Точный статус для вас не имеет значения: суть в том, что собственная планка приватности у оператора выше, чем у легаси-сайта, и у вас нет надёжного способа узнать, какую версию видит конкретный посетитель в конкретный день.

Отдельно в материале PentesterLab The State of JWT Libraries on JWT.io (28 марта 2025) выяснилось, что курируемый jwt.io список библиотек до сих пор рекомендует репозитории, заархивированные в 2018 году, библиотеки, последний раз обновлённые в 2015-м, и как минимум одну реализацию, поддерживающую HMAC-MD5 (алгоритм, которого вообще нет в спецификации JWT). Одна библиотека из списка извлекает значение alg через регулярное выражение вместо парсинга JSON; другая подписывает «случайной строкой случайной длины и payload в качестве секрета». Луи Ниффенеггер, основатель PentesterLab, резюмирует: «Разработчики могут неосознанно выбрать небезопасные или заброшенные библиотеки».

Каталог библиотек — это не та же проблема, что приватность токенов, но он указывает в том же направлении: свойство «популярный и удобный» делает слишком много необоснованной работы в вашей модели доверия.

Что на самом деле означает «клиентский декодер JWT»

По-настоящему клиентский декодер делает три вещи:

  1. Однократно загружает код по HTTPS, а затем работает целиком во вкладке вашего браузера.
  2. Выполняет декодирование на JavaScript через atob() (или хелпер для Base64URL) и JSON.parse() для сегментов заголовка и payload.
  3. Опционально проверяет подписи через Web Crypto API — тот же проаудированный примитив, который защищает ваши TLS-рукопожатия.

Никакого fetch, никакого XHR, никакого navigator.sendBeacon, никакого аналитического запроса с вашим токеном. Байты токена никогда не покидают память вкладки.

Это не дефолт. Это свойство, которым инструмент либо обладает, либо нет, и которое вы можете проверить сами меньше чем за минуту.

Как проверить, что инструмент для JWT действительно клиентский (рецепт за 60 секунд)

Это самый полезный навык в этой статье. Применяйте его к каждому инструменту, который касается учётных данных, включая инструменты remove.sh.

Способ 1: Тест в режиме полёта

  1. Откройте инструмент в новой вкладке.
  2. Дождитесь полной загрузки страницы.
  3. Отключите Wi-Fi или включите режим полёта.
  4. Вставьте токен и декодируйте.

Если расшифрованный заголовок и payload появляются — инструмент делает работу локально. Если вы видите спиннер или ошибку — инструмент делает серверный вызов, скорее всего, с вашим токеном в теле запроса. Это самая быстрая и самая однозначная проверка.

Способ 2: Аудит сети в DevTools

  1. Откройте DevTools (F12 или Cmd+Option+I).
  2. Перейдите на вкладку Network.
  3. Установите фильтр Fetch/XHR.
  4. Нажмите Clear (значок запрета), а затем вставьте свой токен.

Клиентский декодер покажет ноль Fetch/XHR-запросов во время декодирования. Если вы видите запрос — нажмите на него и проверьте вкладку Payload: ваш токен может оказаться в теле запроса или query-строке. Даже если его там нет, инструмент обращается к сети на каждое нажатие клавиши, и этого достаточно, чтобы дисквалифицировать его для чувствительных задач.

Способ 3: Прочитайте адресную строку

Если инструмент кладёт токен прямо в URL — ?token=eyJhbG... — этот токен теперь в истории вашего браузера, в истории шелла, если вы скопировали URL, в заголовке Referer, отправляемом каждому аналитическому скрипту на следующей странице, и в любом менеджере буфера обмена, который вы используете. Инструменты, использующие фрагменты URL (#token=...), лучше, потому что фрагменты не отправляются на серверы, но query-параметры — это мгновенная дисквалификация.

Попробуйте сами: Откройте JWT Decoder на remove.sh и проведите все три проверки. Панель Network остаётся пустой после загрузки страницы, URL никогда не содержит ваш токен, и страница работает в самолёте. Это проверяемое свойство, а не маркетинговое заявление.

Альтернатива jwt.io с приоритетом приватности: декодирование в браузере

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

JWT Decoder от remove.sh построен на тех же примитивах, что описаны в этой статье. Он использует atob() для сегментов Base64URL, JSON.parse() для заголовка и payload, и Web Crypto API для проверки HMAC-подписей (HS256/HS384/HS512). Токены никогда не сериализуются в URL. Никакого аналитического SDK, читающего поле ввода. Инструмент сообщает статус истечения, сравнивая клейм exp с вашими локальными часами, и подписывает зарегистрированные клеймы (iss, sub, aud, iat, nbf, jti), чтобы вам не приходилось их запоминать.

Чего он не делает на сегодня — это не проверяет RSA или ECDSA-подписи (RS256/ES256). Для асимметричных подписей у вас есть два разумных варианта, оба разобраны в следующем разделе.

Если ваш токен обёрнут в Authorization: Bearer eyJhbG..., вставляйте всё целиком — декодер автоматически отрезает префикс Bearer . Если у вас есть только Base64-сегменты и вы хотите посмотреть их в сыром виде, Base64 Encoder & Decoder декодирует отдельные сегменты, не пытаясь интерпретировать их как JWT, что полезно при подозрении на повреждённый токен.

Проверка подписей без раскрытия секрета

Декодирование говорит вам, что токен заявляет. Верификация говорит, стоит ли ему доверять. Это разные операции, и у них разные ставки в плане приватности.

HMAC (HS256/HS384/HS512)

Проверка HMAC требует общего секрета. Это опасный случай: утечка секрета означает, что любой может выпускать валидные токены для вашего сервиса. Никогда не вставляйте HMAC-секрет в онлайн-инструмент, локальность которого вы не можете подтвердить.

JWT Decoder от remove.sh проверяет HMAC-подписи через Web Crypto API. Секрет остаётся в памяти вашей вкладки и никогда не сериализуется в сеть. Подтвердить это можно, проведя тест в режиме полёта одновременно с заполненными токеном и секретом — верификация всё равно завершится.

Если вам нужна гарантия выше, чем даже у проаудированной вкладки браузера, спускайтесь к локальному рецепту с openssl ниже, чтобы пересчитать HMAC от секции «заголовок-payload» и сравнить его с сегментом подписи самостоятельно.

RSA / ECDSA (RS256, RS384, RS512, ES256, ES384, ES512)

Асимметричная верификация требует только публичного ключа, который по определению не является секретом. Это лёгкий случай для онлайн-инструментов: данные, проходящие через них, сами по себе не чувствительны. Единственное, что вы должны защищать — это сам токен, который содержит одни и те же клеймы независимо от алгоритма.

Пока remove.sh не выпустит проверку RSA/ECDSA в декодере, самый чистый локальный вариант — step crypto:

step crypto jwt verify \
  --jwks https://your-issuer.example.com/.well-known/jwks.json \
  --iss https://your-issuer.example.com \
  --aud your-audience < token.txt

Или с openssl и публичным ключом в формате PEM:

# Extract the signed portion (header.payload)
SIGNED=$(cut -d. -f1,2 token.txt)

# Extract the signature, decode Base64URL
cut -d. -f3 token.txt | tr '_-' '/+' | base64 -d > sig.bin

# Verify with openssl
echo -n "$SIGNED" | openssl dgst -sha256 -verify pubkey.pem -signature sig.bin

Оба варианта работают офлайн. Оба позволяют проверить токен, ни разу не вставив его на сайт.

Локальное декодирование JWT через CLI

Для разовых проверок без браузера два однострочника покрывают большинство случаев.

Красиво вывести заголовок:

cut -d. -f1 token.txt | tr '_-' '/+' | base64 -d 2>/dev/null | jq .

Красиво вывести payload:

cut -d. -f2 token.txt | tr '_-' '/+' | base64 -d 2>/dev/null | jq .

Шаг tr '_-' '/+' конвертирует Base64URL в стандартный Base64, который ожидают большинство декодеров. 2>/dev/null гасит предупреждение base64: invalid input, которое появляется, если сегмент не заполнен паддингом, — jq всё равно корректно выведет JSON.

Если у вас нет jq, JSON Formatter на remove.sh красиво отформатирует вывод любой из этих команд в браузере без отправки куда-либо. Передайте в буфер обмена через pbcopy (macOS) или xclip -sel clip (Linux), а затем вставьте.

Как безопасно поделиться JWT в баг-репорте

Часто разработчики тянутся к jwt.io не для отладки — а чтобы вставить расшифрованный payload в Slack-тред или тикет, чтобы коллега увидел клеймы. Есть более безопасный рецепт.

  1. Декодируйте токен локально одним из способов выше.
  2. Копируйте только расшифрованный JSON — никогда не закодированную форму eyJhbG.... Закодированная форма — это учётные данные; расшифрованный JSON — это просто данные.
  3. Закройте идентифицирующие клеймы перед тем, как делиться: замените sub, email, name, кастомные ID пользователей и тенантов плейсхолдерами (sub: "user-redacted").
  4. Отбросьте сегмент подписи. Его нельзя восстановить из заголовка и payload без секрета или приватного ключа, а значит, отредактированный JSON больше не является пригодным к использованию учётным данным.
  5. Для тестовых фикстур генерируйте свежий токен, подписанный одноразовым секретом. Для воспроизведения бага важна форма, а не значения.

Если коллеге нужно подтвердить «да, это тот же токен, что я вижу у себя», но никто из вас не хочет делиться самим токеном, вставьте закодированный JWT в Hash Generator на remove.sh и обменяйтесь SHA-256-отпечатками. Один и тот же отпечаток означает один и тот же токен; отпечаток ничего не говорит о клеймах или подписи.

Этот подход превращает обмен учётными данными в обмен данными — а именно этого и требует ваша политика безопасности.

Часто задаваемые вопросы

Открытый ли исходный код у jwt.io?

Фронтенд-код сайта опубликован на GitHub, но «open source» не говорит вам, что именно крутится на jwt.io сегодня. Как отметил Джейми Танна, «у нас нет ни малейшего понятия, действительно ли исходный код… используется» на живом сайте. Проверяемость — пустая вкладка Network — это более сильный сигнал, чем публичный репозиторий.

Утекали ли когда-нибудь токены из jwt.io?

Публичных раскрытий инцидентов с утечкой токенов нет. Есть публичное признание мейнтейнеров (GitHub #700), что легаси-обработка через query-параметры URL создавала достаточно серьёзные опасения по приватности, чтобы мотивировать переработку. Отсутствие известного инцидента — это не то же самое, что отсутствие риска.

Можно ли захостить jwt.io у себя?

Да, исходники доступны, но self-hosting решает проблему доверия к сети только если вы реально аудируете сборку и раздаёте её с инфраструктуры, которую сами контролируете. Большинству команд уже проверенный клиентский инструмент даст меньше работы и меньше риска, чем поддержка форка.

Нужен ли секрет, чтобы декодировать JWT?

Нет. Декодирование заголовка и payload требует только декодирования Base64URL и парсинга JSON — обе операции чисто локальные. Секрет нужен только для проверки подписи — это отдельный шаг. Любой инструмент, который запрашивает ваш секрет просто чтобы показать расшифрованные клеймы, делает что-то не так.

Почему мой токен показывается как истёкший, хотя я его только что выпустил?

Клейм exp сравнивается с локальными часами. Декодеры в браузере используют системные часы; CLI-инструменты — часы хоста. Сбитые часы — обычное дело для виртуальных машин и CI-раннеров — заставят валидные токены выглядеть истёкшими и наоборот. Синхронизируйте часы (sudo sntp -sS time.apple.com или sudo ntpdate pool.ntp.org) и проверьте снова.

Реально ли безопасно вставлять токен в инструмент, который вы проверили как клиентский?

Да, с одной оговоркой. Инструмент, клиентский сегодня, может перестать таким быть завтра. Проверку Network-вкладки нужно повторять, когда вы доверяете инструменту особо чувствительный токен, особенно после длительного перерыва. Закладка на инструмент или браузерное расширение, предупреждающее об исходящих запросах с конкретного origin, делают это дешевле.

Как это связано с более широкой позицией remove.sh по приватности?

Та же архитектура применяется ко всем инструментам разработчика на сайте. Развёрнутая аргументация — в Client-Side Developer Tools: The Privacy-First Approach, где подробно разобраны JSON-форматтеры, генераторы хешей, Base64-декодеры и Web Crypto API.

Итог

jwt.io — популярное удобное решение, обрабатывающее класс данных — учётные — для которого «популярное удобство» не является достаточной моделью доверия. Его собственные мейнтейнеры публично написали, что у легаси-сайта есть проблемы с приватностью, серьёзные настолько, что его нужно переделать. Замена — это не переработка jwt.io и не какой-то конкретный конкурент; это привычка.

Привычка такая: прежде чем какой-либо инструмент увидит токен, проведите тест в режиме полёта. Если он работает офлайн — он локальный. Если нет — найдите тот, который работает, или отступите к CLI-однострочнику.

JWT Decoder на remove.sh построен так, чтобы проходить этот тест. Откройте его, отключите сеть, декодируйте токен. Если он по-прежнему работает — а он будет работать — вы только что собственными глазами убедились, что никакая третья сторона никогда не увидит этот JWT.

Это и есть ответ на «альтернатива jwt.io приватность». Не другой бренд. Проверяемое свойство.