Utilities

Small but pervasive helpers — base64url encoding, cryptographic randomness, type guards, JWT claim validation, and the Duration format that powers expiresIn / maxTokenAge / notBeforeIn everywhere in unjwt.

import {
  base64UrlEncode,
  base64UrlDecode,
  base64Encode,
  base64Decode,
  secureRandomBytes,
  concatUint8Arrays,
  textEncoder,
  textDecoder,
  isJWK,
  isJWKSet,
  isSymmetricJWK,
  isAsymmetricJWK,
  isPrivateJWK,
  isPublicJWK,
  isCryptoKey,
  isCryptoKeyPair,
  validateJwtClaims,
  computeDurationInSeconds,
  inferJWSAllowedAlgorithms,
  inferJWEAllowedAlgorithms,
  sanitizeObject,
} from "unjwt/utils";

All of these are also available from the flat barrel unjwt.

Encoding

base64UrlEncode(data)

Encodes Uint8Array | string to Base64URL (no padding, URL-safe alphabet). Returns a string.

base64UrlEncode(new Uint8Array([255, 255])); // "__8"
base64UrlEncode("hello"); // "aGVsbG8"

base64UrlDecode(data, options?)

Decodes a Base64URL string or Uint8Array. Without an options object the return type follows the input: a string input returns a decoded string, a Uint8Array input returns a Uint8Array. Pass { returnAs: "string" } or { returnAs: "uint8array" } (alias "bytes") to force the return type.

base64UrlDecode("aGVsbG8"); // "hello"
base64UrlDecode("aGVsbG8", { returnAs: "uint8array" });
// Uint8Array([104, 101, 108, 108, 111])
base64UrlDecode(new Uint8Array([97, 71, 86, 115, 98, 71, 56]), { returnAs: "string" });
// "hello"

base64Encode(data) / base64Decode(data, options?)

Standard Base64 (with = padding). Same signatures as the URL-safe variants — base64Decode accepts the same { returnAs } options object as base64UrlDecode. Use when interoperating with systems that expect classical Base64.

Randomness

secureRandomBytes

secureRandomBytes(length);

Returns a cryptographically secure Uint8Array of the specified length, via crypto.getRandomValues:

const salt = secureRandomBytes(16); // 128-bit random salt
const cek = secureRandomBytes(32); // 256-bit CEK

Use this — not Math.random(), not a Node-specific API — for any security-sensitive randomness. It works everywhere unjwt runs.

There is no randomBytes alias — use secureRandomBytes.

Concatenation

concatUint8Arrays(...arrays)

Concatenates multiple Uint8Array instances into one. Useful when building custom envelopes or AAD strings without intermediate Buffer allocations:

const aad = concatUint8Arrays(
  textEncoder.encode(base64UrlEncode(protectedHeader)),
  new Uint8Array([0x2e]), // "."
  textEncoder.encode(base64UrlEncode(externalAad)),
);

Singletons

textEncoder / textDecoder

Singleton TextEncoder and TextDecoder instances — reuse them instead of allocating new ones per call:

textEncoder.encode("hello"); // Uint8Array
textDecoder.decode(uint8ArrayBytes); // string

Type guards

FunctionReturnsChecks
isJWK(key)key is JWKObject with kty
isJWKSet(key)key is JWKSetObject with keys: JWK[]
isCryptoKey(key)key is CryptoKeyCryptoKey instance
isCryptoKeyPair(key)key is CryptoKeyPairObject with publicKey + privateKey as CryptoKeys
isSymmetricJWK(key)key is JWK_octJWK with kty: "oct" and k property
isAsymmetricJWK(key)key is JWK_AsymmetricJWK that isn't oct
isPrivateJWK(key)key is JWK_PrivateAsymmetric JWK with a d (private component)
isPublicJWK(key)key is JWK_PublicAsymmetric JWK without d
assertCryptoKey(key)assertionThrows if not a CryptoKey
guards.ts
import { isPrivateJWK, isSymmetricJWK } from "unjwt/utils";

function canSign(k: unknown) {
  return isSymmetricJWK(k) || isPrivateJWK(k);
}

JWT claim validation

validateJwtClaims

validateJwtClaims(claims, options?)

The low-level claim validator used inside verify() and decrypt(). Throws JWTError on failure. Checks:

  • exp — expiration
  • nbf — not-before
  • iat — issued-at type check and (with maxTokenAge) age bound
  • iss — issuer
  • sub — subject
  • aud — audience
  • requiredClaims — presence of a named set

Timestamps (exp, nbf, iat) are validated strictly: if present but not a finite number (string, NaN, null), the check throws rather than silently skipping.

typ is part of {@link JWTClaimValidationOptions} but is validated at the verify() / decrypt() layer (against the protected header), not inside validateJwtClaims itself.

JWTClaimValidationOptions

interface JWTClaimValidationOptions {
  audience?: string | string[];
  issuer?: string | string[];
  subject?: string;
  maxTokenAge?: MaxTokenAge; // alias of Duration
  clockTolerance?: number; // seconds; defaults to 0
  typ?: string;
  currentDate?: Date;
  requiredClaims?: string[];
  /**
   * Critical header parameters this caller understands.
   * Verification fails if the token's `crit` header lists a parameter
   * not in this list (RFC 7515 §4.1.11 / RFC 7516 §4.1.13).
   */
  recognizedHeaders?: string[];
}

This same interface is extended by JWSVerifyOptions and JWEDecryptOptions — so the options below work identically everywhere.

The Duration format

expiresIn, notBeforeIn, maxTokenAge, and friends all accept a single type:

type Duration = number | `${number}` | `${number}${Unit}`;

// Unit short forms: s m h D W M Y
// Unit long forms: seconds minutes hours days weeks months years
// (both singular and plural accepted for long forms)

Number and unit must be adjacent — no space. Case matters for the single-letter day/week/month/year units: D, W, M, Y.

Examples that all resolve to 3600 seconds:

3600
"3600"    "3600s"    "3600seconds"
"60m"     "60minutes"
"1h"      "1hour"

And others:

"7D"; // 7 days
"1W"; // 1 week
"3M"; // 3 months (30-day months)
"1Y"; // 1 year (365-day years)
"30s"; // 30 seconds

computeDurationInSeconds(duration)

Converts a Duration to a positive integer number of seconds. Throws if the result is <= 0:

computeDurationInSeconds("1h"); // 3600
computeDurationInSeconds("7D"); // 604800
computeDurationInSeconds(30); // 30
computeMaxTokenAgeSeconds and computeExpiresInSeconds are @deprecated aliases retained for backward compatibility. Prefer computeDurationInSeconds in new code.

Algorithm inference

Used internally to derive allowlists from key shapes when options.algorithms is omitted. Exposed for advanced use — e.g. building a logging layer that reports which algorithms your infrastructure actually exercises.

inferJWSAllowedAlgorithms

inferJWSAllowedAlgorithms(key);

Returns the set of signing algorithms a key can unambiguously produce, or undefined if inference isn't possible (raw bytes, alg-less JWK, lookup function).

inferJWEAllowedAlgorithms

inferJWEAllowedAlgorithms(key);

Returns the set of key-management algorithms a key can unambiguously handle. Passwords infer to the three PBES2-* variants plus "dir"; symmetric keys infer to their specific wrap alg plus "dir".

When these return undefined, callers must pass options.algorithms explicitly — otherwise verification / decryption has no effective algorithm guard.

sanitizeObject

sanitizeObject(obj);

Returns a deep structural copy of obj with the prototype-pollution vectors __proto__, prototype, and constructor stripped at every level. Input is never modified.

unjwt applies this internally to all parsed JWT headers and user-supplied options — you rarely need to call it yourself. Exposed for userland code that handles untrusted JSON.

See also