Skip to main content
Module

x/jose/lib/jwt_claims_set.ts

"JSON Web Almost Everything" - JWA, JWS, JWE, JWT, JWK, JWKS with no dependencies using runtime's native crypto in Node.js, Browser, Cloudflare Workers, Electron, and Deno.
Extremely Popular
Go to Latest
File
import type { JWTPayload, JWTClaimVerificationOptions, JWEHeaderParameters, JWSHeaderParameters,} from '../types.d.ts'import { JWTClaimValidationFailed, JWTExpired, JWTInvalid } from '../util/errors.ts'import { decoder } from './buffer_utils.ts'import epoch from './epoch.ts'import secs from './secs.ts'import isObject from './is_object.ts'
const normalizeTyp = (value: string) => value.toLowerCase().replace(/^application\//, '')
const checkAudiencePresence = (audPayload: unknown, audOption: unknown[]) => { if (typeof audPayload === 'string') { return audOption.includes(audPayload) }
if (Array.isArray(audPayload)) { // Each principal intended to process the JWT MUST // identify itself with a value in the audience claim return audOption.some(Set.prototype.has.bind(new Set(audPayload))) }
return false}
export default ( protectedHeader: JWEHeaderParameters | JWSHeaderParameters, encodedPayload: Uint8Array, options: JWTClaimVerificationOptions = {},) => { const { typ } = options if ( typ && (typeof protectedHeader!.typ !== 'string' || normalizeTyp(protectedHeader!.typ) !== normalizeTyp(typ)) ) { throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', 'typ', 'check_failed') }
let payload!: { [propName: string]: unknown } try { payload = JSON.parse(decoder.decode(encodedPayload)) } catch { // }
if (!isObject(payload)) { throw new JWTInvalid('JWT Claims Set must be a top-level JSON object') }
const { issuer } = options if (issuer && !(<unknown[]>(Array.isArray(issuer) ? issuer : [issuer])).includes(payload.iss!)) { throw new JWTClaimValidationFailed('unexpected "iss" claim value', 'iss', 'check_failed') }
const { subject } = options if (subject && payload.sub !== subject) { throw new JWTClaimValidationFailed('unexpected "sub" claim value', 'sub', 'check_failed') }
const { audience } = options if ( audience && !checkAudiencePresence(payload.aud, typeof audience === 'string' ? [audience] : audience) ) { throw new JWTClaimValidationFailed('unexpected "aud" claim value', 'aud', 'check_failed') }
let tolerance: number switch (typeof options.clockTolerance) { case 'string': tolerance = secs(options.clockTolerance) break case 'number': tolerance = options.clockTolerance break case 'undefined': tolerance = 0 break default: throw new TypeError('Invalid clockTolerance option type') }
const { currentDate } = options const now = epoch(currentDate || new Date())
if ((payload.iat !== undefined || options.maxTokenAge) && typeof payload.iat !== 'number') { throw new JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid') }
if (payload.nbf !== undefined) { if (typeof payload.nbf !== 'number') { throw new JWTClaimValidationFailed('"nbf" claim must be a number', 'nbf', 'invalid') } if (payload.nbf > now + tolerance) { throw new JWTClaimValidationFailed( '"nbf" claim timestamp check failed', 'nbf', 'check_failed', ) } }
if (payload.exp !== undefined) { if (typeof payload.exp !== 'number') { throw new JWTClaimValidationFailed('"exp" claim must be a number', 'exp', 'invalid') } if (payload.exp <= now - tolerance) { throw new JWTExpired('"exp" claim timestamp check failed', 'exp', 'check_failed') } }
if (options.maxTokenAge) { const age = now - payload.iat! const max = typeof options.maxTokenAge === 'number' ? options.maxTokenAge : secs(options.maxTokenAge)
if (age - tolerance > max) { throw new JWTExpired( '"iat" claim timestamp check failed (too far in the past)', 'iat', 'check_failed', ) }
if (age < 0 - tolerance) { throw new JWTClaimValidationFailed( '"iat" claim timestamp check failed (it should be in the past)', 'iat', 'check_failed', ) } }
return <JWTPayload>payload}