В спецификации JWT (JSON Web Token, RFC 7519), на которой основана работа библиотеки Jose в JavaScript, понятие claims является центральным элементом структуры токена. Claims представляют собой набор утверждений (свойств), содержащихся в полезной нагрузке (payload) JWT и описывающих субъект, контекст и дополнительные данные токена.
JWT-токен логически состоит из трёх частей:
Именно в payload находятся claims — пары ключ-значение, формирующие семантику токена.
Библиотека Jose работает строго в рамках этой модели и предоставляет инструменты для создания, подписи, шифрования и проверки JWT, JWS и JWE, где claims являются основным объектом обработки на уровне payload.
Claims в JWT делятся на три категории:
Эта классификация не является особенностью Jose, но напрямую влияет на то, как данные формируются, валидируются и интерпретируются при использовании функций библиотеки.
Хотя основной акцент делается на публичных и приватных claims, зарегистрированные claims задают базовую семантику токена и часто используются совместно с ними.
К ним относятся:
iss (issuer) — издатель токенаsub (subject) — субъект токенаaud (audience) — получательexp (expiration time) — срок действияnbf (not before) — время начала действияiat (issued at) — время выпускаjti (JWT ID) — уникальный идентификаторВ Jose эти поля не обрабатываются автоматически как обязательные, но
могут проверяться через параметры в функциях jwtVerify,
jwtDecrypt, jwtSign и аналогичных.
Публичные claims (public claims) предназначены для использования в межсистемных сценариях, где важно избежать конфликтов имен и обеспечить интероперабельность.
Типичный подход — использование URI в качестве ключа:
{
"https://example.com/role": "admin",
"https://example.com/permissions": ["read", "write"]
}
Такой формат снижает вероятность конфликта между различными системами, которые могут использовать одинаковые короткие ключи.
Jose не накладывает ограничений на структуру payload. Любые дополнительные поля считаются частью claims и включаются в подписываемый или шифруемый объект.
Пример создания JWT с публичными claims:
import { SignJWT } from 'jose'
const token = await new SignJWT({
'https://example.com/role': 'admin',
'https://example.com/scopes': ['users:read', 'users:write']
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('2h')
.sign(secretKey)
При верификации Jose возвращает payload без изменения структуры:
import { jwtVerify } from 'jose'
const { payload } = await jwtVerify(token, secretKey)
console.log(payload['https://example.com/role'])
Публичные claims в таком контексте становятся частью проверяемой подписи и не отличаются по механике от других полей payload.
Приватные claims (private claims) предназначены для внутреннего использования между согласованными сторонами. Они не стандартизированы и не должны пересекаться с зарегистрированными или публичными именами.
Пример структуры:
{
"role": "admin",
"userId": 42,
"plan": "premium",
"internalFlags": {
"beta": true
}
}
Jose рассматривает приватные claims как часть payload без какой-либо семантической интерпретации. Библиотека не различает, является ли ключ публичным или приватным — ответственность за структуру лежит на разработчике.
Пример подписания токена с приватными claims:
const token = await new SignJWT({
userId: 42,
role: 'admin',
plan: 'premium',
internalFlags: { beta: true }
})
.setProtectedHeader({ alg: 'RS256' })
.setSubject('42')
.setIssuedAt()
.sign(privateKey)
При проверке:
const { payload } = await jwtVerify(token, publicKey)
if (payload.role === 'admin') {
// доступ к административным функциям
}
Важный аспект при использовании Jose заключается в том, что различие между публичными и приватными claims не реализуется на уровне библиотеки. Оно существует исключительно на уровне проектирования данных.
| Тип claims | Область применения | Риск конфликта | Формат |
|---|---|---|---|
| Публичные | Межсистемный обмен | Средний | URI или стандартизированные ключи |
| Приватные | Внутренние системы | Высокий при интеграциях | Любые строки |
Jose предоставляет универсальный механизм сериализации и криптографической защиты, но не занимается нормализацией структуры payload.
Все claims, независимо от типа, входят в вычисление подписи JWS или в шифруемый блок JWE.
Пример JWE с приватными claims:
import { EncryptJWT } from 'jose'
const token = await new EncryptJWT({
role: 'admin',
permissions: ['write', 'delete']
})
.setProtectedHeader({ alg: 'RSA-OAEP', enc: 'A256GCM' })
.setExpirationTime('1h')
.encrypt(publicKey)
После расшифровки:
import { jwtDecrypt } from 'jose'
const { payload } = await jwtDecrypt(token, privateKey)
console.log(payload.permissions)
При использовании Jose в реальных системах основная проблема возникает не на уровне криптографии, а на уровне организации структуры claims.
app_,
internal_)Пример структурированного payload:
{
"sub": "42",
"exp": 1710000000,
"https://api.example.com/roles": ["admin"],
"app_userProfile": {
"theme": "dark"
},
"app_permissions": ["read", "write"]
}
При вызове jwtVerify возможна дополнительная проверка
claims через параметры:
await jwtVerify(token, key, {
issuer: 'https://auth.example.com',
audience: 'api.example.com'
})
В этом случае registered claims участвуют в автоматической проверке, а публичные и приватные claims проверяются вручную после получения payload.
Jose не вводит различий между типами claims на уровне API. Это принципиальная особенность архитектуры:
Такой подход позволяет использовать одну и ту же реализацию для различных сценариев: авторизация, обмен данными, одноразовые токены, шифрованные контейнеры информации.