Files
skills/docs/security-stack/security/jws/src/serialization/compact/compact.md
2026-05-29 08:20:56 +01:00

2.8 KiB

Compact serialization

(no submodules)

Purpose

RFC 7515 §3.1 + §7.1 compact-form JWS — the three-segment <protected>.<payload>.<signature> string format that is the default representation for JWS-as-JWT and the only form most downstream consumers will ever encounter. This module owns compactDecode (parse), compactVerify (RFC §5.2 verify), and compactSign (RFC §5.1 sign).

Features

  • Strict structural parser: exactly two . separators, URL-alphabet bytes only.
  • Empty middle segment accepted (RFC 7515 §A.5 detached form).
  • Mandatory algorithms allowlist at verify time — algorithm-confusion-CVE defence.
  • compactSign returns the joined string; consumers can split on . to inspect segments.

Usage: Basic Example

import { compactDecode, compactSign, compactVerify } from "@suaveplan/jws";

const token = await compactSign("hello", secret, "HS256");
const decoded = compactDecode(token);
// decoded.protected / decoded.payload / decoded.signature

const result = await compactVerify(token, secret, { algorithms: ["HS256"] });
// result.verified === true

Usage: Advanced Example

import { compactVerify, type KeyResolver, JwsError } from "@suaveplan/jws";

const keyResolver: KeyResolver = async (kid) => keyStore.get(kid);

const verify = async (token: string) => {
    try {
        return await compactVerify(token, undefined, {
            algorithms: ["RS256", "ES256", "EdDSA"],
            allowEmbeddedJwk: false,
            allowX5c: false,
            keyResolver,
            acceptableCriticalHeaders: ["exp", "nbf"],
        });
    } catch (err) {
        if (err instanceof JwsError) {
            if (err.code === "JWS.ALG_NOT_ALLOWED") return null;
            if (err.code === "JWS.SIG_INVALID") return null;
            if (err.code === "JWS.KEY_NOT_FOUND") return null;
        }
        throw err;
    }
};

API Reference

  • compactDecode(token: string): CompactJws — throws JWS.MALFORMED.
  • compactVerify(token: string, key: unknown, opts: VerifyOptions): Promise<VerifyResult>
  • compactSign(payload: string | Uint8Array, key: unknown, alg: string, opts?: SignOptions): Promise<string>

Implementation Notes

Verification enforces the algorithm allowlist BEFORE any cryptographic work — opts.algorithms.includes(header.alg) is checked immediately after parsing the header, so a token presenting alg: "HS256" against an RSA public key cannot even reach the HMAC impl, neutralising the entire alg-confusion class. The pipeline also enforces crit allowlist before alg dispatch, so a token whose crit parameter is unknown to the verifier never runs cryptography either; this matters because cryptographic verification side-channels can leak information about the verifier's state (cache state, branch prediction) even when the verifier rejects the token.