jose в Node.js: JWT, подпись и ключи — мастерская и 9 практических задач
Девять задач и двадцать два варианта: подпись, подмена, сроки, контекст, ключи и права API. Живая криптографическая мастерская, Node.js-сервер и исходники.

jose и JWT на практике: подпись, claims, сроки, ключи и права API. Девять тем, двадцать два варианта, настоящая криптография, исходники Windows/Linux.
jose: что можно получить от библиотеки на практике
Веб-приложение получает JWT и хочет понять, можно ли доверять сообщению: кто его выпустил, для какого API оно предназначено, не изменили ли данные и не истёк ли срок. Отдельный вопрос — разрешена ли этому пользователю конкретная операция. Библиотека jose помогает выполнить криптографическую проверку и проверить заданные параметры токена, а приложение принимает решение о доступе.
Это пригодится в Node.js API, взаимодействии сервисов, интеграции с существующим провайдером входа и проверке подписанных короткоживущих сообщений. Важно заранее знать доверенного издателя и правила своего сервиса. Случайный JWT из запроса не должен сам выбирать ключ, алгоритм и адрес, которым проверяющая сторона начнёт доверять.
В статье есть девять практических тем и двадцать два варианта. В мастерской можно увидеть подмену payload, истечение срока, другую audience, неизвестный ключ и различие 401/403. Исходники включают настоящую браузерную криптографию, работающий Node HTTP API, тесты, команды Windows/Linux и пример организации закрытого учебного сервиса на VPS.
Где использовать: API, сервисы и интеграции
Первый типичный сценарий — ваш API получает access token от уже существующей системы входа. Сервер проверяет подпись доверенным публичным ключом, ожидаемые issuer и audience, срок действия и тип сообщения. После этого он сопоставляет пользователя с объектом запроса: например, имеет ли человек право читать именно этот отчёт. Токен помогает передать утверждения, но правила работы с отчётом остаются в API.
Другой случай — несколько сервисов, которым нужно проверять сообщения одного издателя без передачи каждому его приватного ключа. Асимметричная подпись разделяет роли: один компонент подписывает, другие располагают публичным материалом и проверяют. В лаборатории используем ES256, две пары и публичный JWKS, чтобы показать этот переход на понятных данных.
JWT не нужен для любого обмена между компонентами. Иногда обычная серверная сессия или уже настроенная инфраструктурная аутентификация проще. Выбирайте формат под способ проверки, сроки, отзыв и состав участников. Цель этой статьи — научиться правильно использовать конкретный инструмент, а не заменить им все механизмы доступа в приложении.
JWT, JWS, JWK и JWKS без путаницы
В наших примерах JWT — набор JSON-полей, представленный компактной строкой и защищённый подписью JWS. Строка содержит три части: защищённый заголовок, payload и подпись. Первые две можно прочитать без приватного ключа. Поэтому нельзя класть туда секрет только на основании того, что вся строка выглядит непривычно и длинно.
JWK описывает криптографический ключ в JSON. В зависимости от типа и назначения такой объект может содержать публичную или приватную часть. Мы экспортируем исключительно публичные ключи проверки. JWKS — набор этих объектов, удобный для выбора ключа по согласованным параметрам, в том числе kid. Сам формат JSON не делает ключ доверенным: важен источник, откуда набор получен.
Библиотека также поддерживает другие операции, включая шифрование, но наша лаборатория сосредоточена на подписанных JWT. Здесь нет JWE, системы хранения паролей или полного протокола входа. Названия и возможности проекта сверены с официальным репозиторием jose; рабочие варианты ниже выполняются на зафиксированной версии.
Установка, версии и устройство архива
Проект использует jose 6.2.17, Bootstrap 5.3.9 и esbuild 0.28.2. Для команд выбран Node.js 24 LTS не ниже 24.15.0. Все зависимости закреплены в package-lock.json. Браузер получает локальную сборку, поэтому не загружает библиотеку с внешнего CDN при каждом открытии мастерской. Сборка кода лаборатории занимает около 45 КБ без Bootstrap и исходников для просмотра.
Общий модуль src/lab.mjs содержит создание ключей, подписание, проверку и двадцать два варианта. Его используют браузер и Node API. Это позволяет сравнивать поведение одной функции в двух средах. Интерфейс находится отдельно, HTTP-маршруты отдельно, тесты проверяют и криптографию, и реальные ответы сервера на loopback.
Для готового архива выполняйте npm ci. Если обновляете jose или среду выполнения, повторите положительные и отрицательные тесты до публикации. Успешная установка пакета не подтверждает, что ваш контракт токена сохранился. В частности, проверьте разрешённые алгоритмы, ожидаемые claims, выбор ключа и классификацию ошибок.
npm install --save-exact jose@6.2.17
# В скачанном проекте зависимости уже зафиксированы:
npm ci --ignore-scripts
npm test
npm run demo
npm run build
Мини-мастерская: настоящая криптография в браузере
Выберите задачу и вариант проверки. Страница создаёт две учебные пары ES256 и вызывает настоящий jose через Web Crypto. Можно повторить опыт или создать новые ключи. Заголовок и payload показаны отдельно от правил проверяющей стороны и фактического результата. Так видно, в какой момент прочитанные данные ещё недоверенные и почему проверка их отклонила.
Приватные CryptoKey создаются неэкспортируемыми и остаются в памяти открытой страницы. Они не записываются в localStorage и не передаются серверу. В мастерскую не нужно вводить реальные токены, пароли или ключи. При перезагрузке создаются другие пары; подписи и строки JWT закономерно отличаются. Это учебная среда, а не инструмент проверки ваших рабочих секретов.
В девятой теме подпись проверяется по-настоящему, а HTTP-статус вычисляет локальная функция политики. Браузер не отправляет этот запрос к API. Такой механизм явно подписан. В архиве та же функция подключена к настоящему Node HTTP-серверу, и его ответы проверены отдельными тестами. JSON результата и исходники можно скачать со страницы.
Опыт 1. Decode читает данные, но не создаёт доверие
В первом варианте decodeJwt разбирает payload обычного учебного токена. Во втором меняется поле scope, после чего строка снова декодируется без ошибки. Это ожидаемо: функция чтения не проверяла подпись. Видимый admin:all является только содержимым входа, а не доказанным правом пользователя. Мастерская сохраняет результат DECODE_ONLY и явную отметку verified:false.
Такое чтение полезно для диагностики формата, показа технических деталей или предварительного выбора обработчика в строго ограниченной архитектуре. Но выполнение защищённой операции должно опираться на результат полноценной проверки. Особенно опасно брать роль из decode и считать, что сам факт трёх частей JWT делает её достоверной.
Сравните этот опыт с третьим: тот же изменённый payload не пройдёт проверку старой подписи. Полезный тест собственного приложения — убедиться, что подмена scope действительно не открывает доступ. Если интерфейс скрывает кнопку по декодированному полю, это может быть лишь удобством отображения; сервер всё равно обязан независимо проверять запрос.
import { decodeJwt, decodeProtectedHeader } from 'jose';
const header = decodeProtectedHeader(token);
const payload = decodeJwt(token);
// Это только чтение структуры. Подпись здесь не проверена.
// Не разрешайте операцию на основании payload.scope из этого кода.
Опыт 2. Подписать приватным, проверить публичным
Вторая тема показывает разделение ролей ключей. Токен подписывается приватным ключом пары A. Проверка публичным A успешна, проверка публичным B отклоняет подпись. Оба ключа имеют подходящий тип и алгоритм, но принадлежат разным парам. Это наглядно объясняет, почему «у меня тоже есть ключ ES256» не является основанием доверять чужому токену.
В реальной системе API обычно не должен уметь подписывать сообщения издателя только ради проверки. Он получает публичный материал по заранее настроенному доверенному каналу. Компонент, которому нужен приватный ключ, имеет отдельный жизненный цикл и ограниченный доступ. Демонстрационная страница совмещает роли ради обучения; переносить такую архитектуру в рабочую авторизацию буквально не следует.
Ниже компактный вариант создания учебной пары. В production ключи не генерируют заново на каждый HTTP-запрос. Они управляются согласно выбранному механизму хранения, ротации и восстановления. В лаборатории обновление пары специально доступно кнопкой, чтобы показать, что прежняя подпись связана с прежним ключом.
import { generateKeyPair, SignJWT, jwtVerify } from 'jose';
const { publicKey, privateKey } = await generateKeyPair('ES256', {
extractable: false,
});
const token = await new SignJWT({ fixture: true })
.setProtectedHeader({ alg: 'ES256' })
.setExpirationTime('2m')
.sign(privateKey);
const verified = await jwtVerify(token, publicKey, {
algorithms: ['ES256'],
});
Опыт 3. Payload изменили, подпись оставили
Третья тема сначала проверяет исходный токен, затем меняет scope в средней части строки. Заголовок и подпись остаются прежними. Payload по-прежнему читается как JSON, но jwtVerify возвращает ошибку проверки подписи. Это не ошибка работы мастерской: отрицательный результат и есть ожидаемое поведение корректного проверяющего компонента.
Подпись связывает защищённые данные с ключом. Преобразование JSON в base64url не является шифрованием и не защищает от редактирования само по себе. Поэтому важен именно вызов проверки, а не наличие красивого формата или известного поля alg. В тестах лаборатории ожидается конкретный класс ошибки ERR_JWS_SIGNATURE_VERIFICATION_FAILED.
Для пользовательского интерфейса не обязательно показывать весь криптографический стек. Достаточно понятного сообщения о недействительном токене и следующего допустимого действия. Подробность полезна разработчику, но полный JWT не должен автоматически попадать в логи. В наших примерах данные вымышленные, поэтому их можно открыто исследовать на странице.
// Только отрицательный учебный тест: меняем payload без подписи.
import { base64url, decodeJwt } from 'jose';
const parts = token.split('.');
parts[1] = base64url.encode(JSON.stringify({
...decodeJwt(token), scope: 'admin:all',
}));
const altered = parts.join('.');
// jwtVerify(altered, trustedPublicKey, policy) должен отклонить подпись.
Опыт 4. Exp, nbf и часы проверяющего сервиса
Время — отдельное условие при корректной подписи. exp ограничивает срок использования, nbf задаёт момент, раньше которого токен не принимается. Мастерская показывает четыре варианта: действующий токен, истёкший, ещё не начавший действовать и небольшую погрешность часов. В последнем exp прошёл две секунды назад, но явно разрешён допуск пять секунд.
Для воспроизводимости браузер использует фиксированное время 1 ноября 2026 года, 08:00 UTC. Эти часы подписаны в интерфейсе и передаются в currentDate. Поэтому через месяц пример не станет неожиданно красным. Настоящий Node API в архиве использует текущее время сервера. Не переносите фиксированную дату из лаборатории в рабочую проверку.
Clock tolerance предназначен для ограниченной разницы часов, а не для бессрочного продления доступа. Подберите его под реальные условия и синхронизацию времени. Поле iat сообщает момент выпуска; наличие iat само по себе не заменяет nbf или политику максимального возраста. В нашем контракте обязательные поля перечислены явно, а время проверяется отдельно.
const { payload } = await jwtVerify(token, trustedPublicKey, {
algorithms: ['ES256'],
issuer: expectedIssuer,
audience: expectedAudience,
requiredClaims: ['sub', 'iat', 'exp', 'nbf'],
clockTolerance: 5,
// currentDate намеренно не задан: используется реальное время.
});
Опыт 5. Issuer и audience защищают границу контекста
Корректная подпись ещё не говорит, что токен подходит вашему API. В пятой теме один и тот же подписанный токен проверяется с правильным контекстом, другим ожидаемым issuer и другой audience. Во втором и третьем случаях данные отклоняются с указанием соответствующего claim. Это помогает увидеть разницу между «кто-то подписал» и «сообщение предназначено для нашего сервиса».
Issuer определяет ожидаемого издателя в вашей архитектуре. Audience описывает получателя или получателей, для которых выпускается токен. Значения устанавливаются конфигурацией проверяющей стороны. Если приложение просто берёт iss из токена и тут же объявляет его ожидаемым, смысл проверки теряется. Доверие не должно возникать из самоподтверждения входных данных.
В многосервисной системе договоритесь о точном формате строк, их смене и тестах несовпадения. Особенно полезны отрицательные примеры, где подпись действительна, а контекст другой. Они обнаруживают ошибки, которые не видны в единственном счастливом сценарии «получили токен — получили 200».
await jwtVerify(token, publicKey, {
algorithms: ['ES256'],
issuer: 'https://issuer.example.invalid',
audience: 'aen-report-api',
typ: 'at+jwt',
requiredClaims: ['sub', 'iat', 'exp', 'nbf'],
});
Тип сообщения и обязательные поля
В лаборатории защищённый заголовок содержит typ:at+jwt, а проверка ожидает именно эту метку. Это дополнительное разделение назначения сообщения. Здесь оно не означает, что мы реализовали полный профиль OAuth access token: у такого профиля есть дополнительные требования, а нашей задачей остаётся демонстрация конкретных проверок и API-политики.
Список requiredClaims делает контракт явным. Если приложению обязательно нужны sub и ограниченный срок, нельзя полагаться на то, что издатель всегда случайно их добавит. При этом наличие поля ещё не решает всю предметную задачу: строка subject должна соответствовать понятной модели пользователя, а его доступ к конкретной организации проверяется вашим кодом.
Старайтесь разделять разные виды токенов правилами проверки. Токен для одной операции не должен приниматься в другом месте только потому, что подписан той же инфраструктурой. Практические рекомендации по алгоритмам, контексту и видам JWT описаны в RFC 8725. Примеры статьи показывают эти границы на небольшом проверяемом контракте.
Опыт 6. Список алгоритмов задаёт приложение
Наши токены подписаны ES256. Первый вариант разрешает ES256, второй допускает только RS256 и получает ERR_JOSE_ALG_NOT_ALLOWED. Это демонстрирует политику проверяющей стороны. Заголовок alg описывает полученное сообщение, но не должен заставлять сервер автоматически принимать любой алгоритм, который в нём указан.
Не составляйте разрешённый список путём копирования alg из входного токена. Список определяют требования интеграции и доверенный набор ключей. Слишком широкий набор усложняет понимание гарантий и миграцию. Для лаборатории выбран один алгоритм, чтобы ошибка типа ключа не маскировала смысл проверки. При реальном переходе между алгоритмами нужен отдельный план и отрицательные тесты.
В статье мы не сравниваем производительность алгоритмов и не объявляем ES256 универсальным выбором. Он удобен для демонстрации асимметричной подписи в Node и Web Crypto. Решение для вашей системы зависит от провайдера, поддерживаемых сред и требований к управлению ключами, а не от компактности одного примера.
const policy = {
algorithms: ['ES256'], // конфигурация сервиса, а не значение из JWT
issuer: expectedIssuer,
audience: expectedAudience,
};
const result = await jwtVerify(token, trustedPublicKey, policy);
Опыт 7. Публичный JWK можно передать проверяющей стороне
В седьмом опыте публичный ключ экспортируется в JSON, затем импортируется обратно для проверки. Правильный JWK подтверждает подпись, публичный ключ другой пары — нет. В интерфейсе видны тип кривой, координаты, kid, назначение и алгоритм. Приватного параметра d в публикуемом материале нет; это дополнительно проверяется тестом.
У JWK нет магического признака доверенности. Если злоумышленник подпишет токен своим ключом и приложит его публичную часть, проверка этим же ключом может математически пройти. Приложение должно заранее знать, каким издателям и источникам ключей оно доверяет. Поэтому пример использует две локально созданные пары и явный выбор доверенного материала.
Неэкспортируемость приватного CryptoKey помогает показать правильную границу API в браузере, но не делает страницу системой безопасного хранения рабочих секретов. Код с доступом к объекту всё ещё способен вызвать подпись. Учебная мастерская вообще не должна получать ваши реальные ключи. На сервере управление ими организуется отдельно от публичного интерфейса.
import { exportJWK, importJWK } from 'jose';
const publicJwk = {
...await exportJWK(publicKey),
kid: 'demo-A', alg: 'ES256', use: 'sig',
};
const verifier = await importJWK(publicJwk, 'ES256');
await jwtVerify(token, verifier, policy);
Опыт 8. JWKS, kid и плановая смена ключа
Токен в восьмой теме подписан ключом B. Сначала доверенный набор содержит только A: подходящего ключа для kid не находится, и проверка возвращает ERR_JWKS_NO_MATCHING_KEY. Во втором варианте набор содержит оба публичных ключа, после чего B успешно выбирается. Ключ из заголовка не скачивается автоматически по неизвестному адресу.
Для плановой ротации обычно сначала распространяют публичный B, затем начинают подписывать им новые сообщения, оставляя A на согласованный период жизни старых токенов. После этого A удаляют. Точные сроки зависят от exp, кэширования и способа обновления проверяющих сервисов. Компрометация ключа требует отдельного аварийного процесса; обычная плавная ротация не решает отзыв всех опасных токенов сама по себе.
В лаборатории используется createLocalJWKSet с локальным набором. Поэтому здесь не проверяются сеть, таймауты и кэш удалённого JWKS. Для реального провайдера адрес доверенного набора задают конфигурацией. Подробности выбора публичного ключа описаны в документации createLocalJWKSet.
import { createLocalJWKSet, jwtVerify } from 'jose';
const trusted = createLocalJWKSet({ keys: [publicA, publicB] });
const result = await jwtVerify(tokenSignedByB, trusted, {
algorithms: ['ES256'], issuer: expectedIssuer, audience: expectedAudience,
});
Опыт 9. Подпись, 401 и 403 — разные решения
В последней теме три варианта. Без токена функция возвращает 401. С правильной подписью, но без report:read — 403. С нужным scope — 200 и вымышленный отчёт. Подпись во втором случае верна; отказ относится к праву операции. Такой порядок помогает не смешивать ошибку аутентификации и запрет уже распознанному участнику.
Scope разбивается на отдельные элементы и сравнивается точно. Строка report:read-all не должна случайно удовлетворять проверке report:read через includes по всей строке. Этот отрицательный случай присутствует в тестах. После проверки scope реальный сервис часто должен проверить организацию, владельца и состояние самого объекта — одного общего права чтения может быть недостаточно.
На странице статус является результатом локальной функции, а не выполненного сетевого запроса. Это подписано рядом с вариантом. В следующем разделе эта же функция используется реальным HTTP-сервером из архива. Поэтому можно сначала понять логику в браузере, затем проверить заголовки и ответы через PowerShell, curl или свою коллекцию API-запросов.
const { payload } = await verify(token);
const scopes = typeof payload.scope === 'string'
? payload.scope.split(/\s+/) : [];
if (!scopes.includes('report:read')) {
return { status: 403, body: { error: 'insufficient_scope' } };
}
// Затем проверяются права на конкретный объект и данные ответа.
Настоящий Node API в исходниках
Сервер запускается командой npm run api и слушает только 127.0.0.1:8523. У него есть health, публичный JWKS, endpoint учебного токена и защищённый report. Он использует настоящее время, проверяет Bearer-строку, ограничивает её длину и возвращает короткую ошибку клиенту. Полные токены не пишутся в журнал, ответы имеют Cache-Control:no-store.
Маршрут demo-token специально выдаёт фиксированные учебные варианты без пароля: valid, forbidden и expired. Это генератор фикстур для локального опыта, не готовая авторизация. Его нельзя публиковать как рабочую систему входа. Перед production-интеграцией его удаляют и подключают уже спроектированный доверенный issuer и механизм получения токенов.
После перезапуска сервера создаются новые пары, поэтому старый учебный токен перестанет проходить проверку. Браузерные и серверные ключи также разные: токен со страницы не предназначен для локального Node API. Этот разрыв полезен для обучения, но в рабочей системе постоянство и смена ключей должны управляться предсказуемо.
npm ci --ignore-scripts
npm run api
# http://127.0.0.1:8523/health
# GET /demo-token?mode=valid
# GET /report + Authorization: Bearer <учебный токен>
# Остановить процесс: Ctrl+C
Windows: пройти реальный запрос в PowerShell
Откройте два терминала в каталоге проекта. В первом работает API. Во втором получите учебный токен и передайте его в заголовке Authorization. Invoke-RestMethod разбирает JSON, поэтому вручную копировать длинную строку не нужно. При valid сервер вернёт отчёт, сумму в копейках, валюту и вымышленный subject.
Затем замените режим на forbidden или expired и повторите запрос. В первом случае увидите 403, во втором 401. PowerShell может оформить неуспешный HTTP-ответ как исключение команды — это ожидаемая оболочка над результатом сервера, а не ошибка криптографической библиотеки. Для подробного просмотра статуса можно использовать Invoke-WebRequest или curl.exe.
В реальном проекте не вставляйте рабочий Bearer-токен в общедоступную команду, тикет или снимок экрана. Здесь строки временные и учебные. После проверки очистите переменную или закройте терминал. Если API перезапустился, получите новый токен, поскольку пара ключей была заменена.
$demo = Invoke-RestMethod 'http://127.0.0.1:8523/demo-token?mode=valid'
Invoke-RestMethod 'http://127.0.0.1:8523/report' -Headers @{
Authorization = "Bearer $($demo.token)"
}
# Сравнить отказ в праве:
$demo = Invoke-RestMethod 'http://127.0.0.1:8523/demo-token?mode=forbidden'
Invoke-WebRequest 'http://127.0.0.1:8523/report' -Headers @{
Authorization = "Bearer $($demo.token)"
}
Remove-Variable demo
Linux: curl, Node и воспроизводимый запуск
На Linux используется тот же package-lock и та же команда API. В примере ниже curl получает JSON, небольшой Node-фрагмент извлекает token, затем второй curl показывает заголовки и тело защищённого ответа. Таким образом опыт не зависит от установки jq, хотя при наличии jq команду можно сократить.
Проверка проекта выполнялась в чистом контейнере Node 24.22.2, зависимости устанавливались заново через npm ci. Windows использовал Node 24.19.0. Оба запуска прошли один набор тестов и дали одинаковые статусы двадцати двух вариантов. Подписи и ключи между запусками различаются, поэтому их побайтовое совпадение не является ожидаемым результатом.
Для просмотра браузерной мастерской раздавайте готовую public по HTTPS или localhost. File-протокол и незащищённый удалённый HTTP могут не предоставить нужный Web Crypto-контекст. Отдельный SSH-туннель позволяет проверить закрытый сервер с рабочей машины без открытия учебного порта всему интернету.
npm ci --ignore-scripts
npm test
npm run build
npm run api
# Во втором терминале:
token=$(curl -fsS 'http://127.0.0.1:8523/demo-token?mode=valid' |
node -e 'let s=""; process.stdin.on("data",c=>s+=c).on("end",()=>process.stdout.write(JSON.parse(s).token));')
curl -i -H "Authorization: Bearer $token" http://127.0.0.1:8523/report
unset token
Ошибки: диагностировать причину, не раскрывая токен
В мастерской намеренно видны коды ошибок jose: подпись, срок, claim, алгоритм и отсутствующий ключ. Это помогает разработчику понять, на каком условии остановилась проверка. Клиенту рабочего API обычно достаточно короткого invalid_token и корректного статуса. Подробная внутренняя причина не должна сопровождаться автоматической публикацией полного Bearer-токена.
Для диагностики сохраняйте безопасный ID запроса, тип ошибки, ожидаемый контекст и версию конфигурации. Если проблема началась после ротации, сравните kid с известным набором и моментом его обновления. Если истекает много токенов, проверьте время серверов и жизненный цикл выдачи. Не лечите все отказы отключением проверки exp или расширением списка алгоритмов.
Разделяйте отказ проверки и ошибку инфраструктуры. Например, недоступный удалённый JWKS — другая ситуация, чем математически неверная подпись. Наш локальный набор не демонстрирует сетевые отказы, поэтому перенос на удалённый источник требует дополнительных тестов таймаутов, кэша и наблюдаемости.
Linux/VPS: закрытый сервис и понятный жизненный цикл
Для лаборатории на VPS создайте отдельного пользователя и каталог проекта, установите зависимости без root и оставьте API на loopback. Доступ с рабочей машины организуйте через SSH-туннель. В архиве есть пример systemd-службы с фиксированным рабочим каталогом, ограничением прав и перезапуском при ошибке. Перед использованием проверьте реальный путь node на сервере.
Настройка NODE_ENV=production сама по себе не делает demo-token безопасной системой входа. Это обычный параметр окружения, а не переключатель архитектуры авторизации. Рабочая версия требует удалить генератор фикстур, подключить доверенный issuer, хранение ключей и правила доступа. Публичный reverse proxy не должен случайно открыть учебный endpoint.
Статическая мастерская размещается отдельно. Она загружает код и создаёт собственные временные ключи в браузере, поэтому её публикация не требует выставлять Node API наружу. Разделение этих двух способов запуска позволяет дать посетителю интерактивность и одновременно оставить серверные упражнения воспроизводимыми в скачиваемом проекте.
# На рабочей машине, для закрытого API на своём VPS:
ssh -L 8523:127.0.0.1:8523 user@your-vps
# На сервере после установки своего unit:
sudo systemctl start jose-lab
curl -fsS http://127.0.0.1:8523/health
sudo journalctl -u jose-lab -n 30 --no-pager
sudo systemctl stop jose-lab
Что остаётся за пределами JWT-библиотеки
Подписанный JWT не решает автоматически выход из аккаунта и немедленный отзыв доступа. Если токен проверяется только по подписи и сроку, он может оставаться приемлемым до exp. Для немедленного прекращения доступа требуется отдельная стратегия: состояние сессии, версия полномочий, список отзыва или другой согласованный механизм. Выбор зависит от требований, нагрузки и допустимой задержки.
Также отдельно решается хранение токена в клиенте. Cookies, память страницы и другие способы имеют свои риски и требования к защите от XSS/CSRF. Эта статья не объявляет localStorage универсальным хранилищем: мастерская вообще ничего туда не записывает. Парольная аутентификация, восстановление доступа и многофакторная проверка тоже не появляются после установки jose.
Наконец, JWT-подпись не скрывает payload. Передавайте минимум необходимых утверждений и не размещайте в читаемых полях лишние персональные данные или секреты. Для доверенного API важнее короткий понятный контракт, чем большой токен, который пытается заменить все обращения к данным приложения.
Как проверен проект и что проверить при переносе
На Windows и Linux прошли по 48 тестов. Они покрывают двадцать два варианта, конкретные ошибки подписи и claims, отсутствие приватных параметров в JWK, неэкспортируемость приватных CryptoKey, разные поколения ключей, точное сравнение scope и отрицательные случаи. Отдельный тест создаёт настоящий HTTP-сервер на случайном loopback-порту и выполняет четырнадцать запросов, затем закрывает сервер.
В браузере проверка нужна отдельно: доступность Web Crypto, работа переключателей, понятность отрицательных результатов, мобильная компоновка и скачивание исходников. Node-тест не доказывает удобство интерфейса. Сводки Node-выполнения находятся в public/runtime-node.json и runtime-linux.json; они не подменяют живую работу браузерной мастерской и не содержат токенов или приватных ключей.
Перед интеграцией добавьте свои issuer, audience, типы сообщений, объектные права, политику часов и ротации. Повторите отрицательные проверки, особенно действительную подпись с неправильным контекстом. Для API jose используйте официальное описание jwtVerify, а ожидаемое поведение своего приложения закрепите тестами.
npm ci --ignore-scripts
npm test
npm run demo
npm run build
# Повторение сводки с отметкой Linux:
node tools/demo.mjs --linux
# Сам флаг не меняет ОС и не является Linux-проверкой на Windows.
Скачать, адаптировать и оставить понятные границы
В архив входят общий криптографический модуль, браузерный интерфейс, HTTP API, тесты, инструкции и зафиксированные зависимости. Node_modules не упакован: его восстанавливает npm ci. Приватных файлов ключей в проекте нет, все пары создаются во время выполнения. Для просмотра исходников на странице используются текстовые блоки без исполнения введённого кода.
Начните с одной практической цели: проверить токен известного издателя для одного API. Опишите ожидаемые поля и допустимый алгоритм, определите источник публичных ключей и правила конкретной операции. Затем перенесите проверку в серверный обработчик и добавьте отрицательные тесты. Отдельно запишите, как будут обновляться ключи и что произойдёт при отзыве доступа.
После упражнения остановите свой локальный API и HTTP-просмотрщик через Ctrl+C, а на VPS — только созданную для этой лаборатории службу. Учебные токены не нужны после завершения опыта. Полезный результат работы с jose — не просто полученная длинная строка, а проверяемая последовательность: источник доверия, криптографическая проверка, проверка контекста и решение приложения.


