Flattened JSON Serialization — один из трёх форматов представления JSON Web Signature (JWS), наряду с Compact и General. Он используется в ситуациях, когда требуется передать одну подпись, но при этом сохранить структуру JSON вместо строкового представления.
Структура Flattened JWS включает:
payload — полезная нагрузка (Base64URL)protected — защищённый заголовок (Base64URL)header — незащищённый заголовок (опционально)signature — подпись (Base64URL)Пример:
{
"payload": "eyJzdWIiOiIxMjM0NTY3ODkwIn0",
"protected": "eyJhbGciOiJIUzI1NiJ9",
"signature": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}
Функция flattenedVerify из библиотеки jose
выполняет проверку подписи Flattened JWS и извлекает полезную нагрузку.
Это низкоуровневый инструмент, предоставляющий полный контроль над
процессом верификации.
Основные задачи:
import { flattenedVerify } from 'jose'
Пример минимальной верификации:
const { payload, protectedHeader } = await flattenedVerify(jws, key)
Где:
jws — объект Flattened JWSkey — криптографический ключ (SecretKey, PublicKey,
KeyLike)Объект должен строго соответствовать Flattened JSON Serialization:
const jws = {
payload: '...',
protected: '...',
signature: '...'
}
Ошибки структуры приводят к исключению.
Поддерживаются:
Пример:
const key = new TextEncoder().encode('secret')
или:
import { importSPKI } from 'jose'
const key = await importSPKI(spki, 'RS256')
await flattenedVerify(jws, key, {
algorithms: ['RS256'],
clockTolerance: '5s'
})
Ключевые параметры:
algorithms — список допустимых алгоритмовclockTolerance — допустимая погрешность времениtyp — ожидаемый тип токенаissuer, audience — для дополнительной
проверки payload{
payload: Uint8Array,
protectedHeader: object
}
const data = JSON.parse(new TextDecoder().decode(payload))
Алгоритм берётся из protected заголовка:
{
"alg": "HS256"
}
Если алгоритм не входит в разрешённый список, будет выброшено исключение.
Пример ограничения:
await flattenedVerify(jws, key, {
algorithms: ['ES256']
})
const { protectedHeader } = await flattenedVerify(jws, key)
Содержит:
alg — алгоритм подписиkid — идентификатор ключаtyp — тип токенаЕсли присутствует:
"header": {
"kid": "key1"
}
Он также участвует в процессе, но не защищён подписью.
import { flattenedVerify } from 'jose'
const secret = new TextEncoder().encode('super-secret')
const { payload } = await flattenedVerify(jws, secret)
const data = JSON.parse(new TextDecoder().decode(payload))
import { flattenedVerify, importSPKI } from 'jose'
const publicKey = await importSPKI(spkiPem, 'RS256')
const { payload } = await flattenedVerify(jws, publicKey)
Хотя flattenedVerify не проверяет exp
автоматически, это можно сделать вручную:
const { payload } = await flattenedVerify(jws, key)
const data = JSON.parse(new TextDecoder().decode(payload))
if (data.exp && Date.now() >= data.exp * 1000) {
throw new Error('Token expired')
}
await flattenedVerify(jws, key, {
audience: 'my-app',
issuer: 'auth-server'
})
Если значения не совпадают — ошибка.
Типичные ошибки:
JWSInvalid — некорректная структураJWSSignatureVerificationFailed — подпись невалиднаJOSENotSupported — неподдерживаемый алгоритмПример обработки:
try {
await flattenedVerify(jws, key)
} catch (err) {
console.error(err)
}
1. Ограничение алгоритмов
algorithms: ['RS256']
Исключает атаки с подменой алгоритма.
2. Проверка kid
Используется для выбора ключа:
const { protectedHeader } = await flattenedVerify(jws, key)
if (protectedHeader.kid !== expectedKid) {
throw new Error('Invalid key id')
}
3. Не доверять payload до проверки
Декодирование payload допустимо только после успешной верификации.
4. Использование асимметричных ключей
Предпочтительно для распределённых систем.
| Характеристика | flattenedVerify | compactVerify |
|---|---|---|
| Формат | JSON | строка |
| Подписи | одна | одна |
| Гибкость | высокая | ниже |
| Использование | API, сложные сценарии | простые токены |
flattenedVerify — одна подписьgeneralVerify — несколько подписейimport { flattenedVerify, importJWK } from 'jose'
const jwk = {
kty: 'oct',
k: 'hJtXIZ2uSN5kbQfbtTNWbg'
}
const key = await importJWK(jwk, 'HS256')
const { payload, protectedHeader } = await flattenedVerify(jws, key, {
algorithms: ['HS256']
})
const decoded = JSON.parse(new TextDecoder().decode(payload))
console.log(protectedHeader.alg)
console.log(decoded)
protected