Skip to content

jwt.verify accepts tokens with unrecognized crit header extensions (RFC 7515 §4.1.11) #1032

@zhangjiashuo-cs

Description

@zhangjiashuo-cs

Description

jsonwebtoken@9.0.3 accepts JWTs whose JOSE header contains a crit array listing extension Header Parameters that the library does not understand or process. Per RFC 7515 §4.1.11, such tokens must be rejected:

If any of the listed extension Header Parameters are not understood and supported by the recipient, then the JWS MUST be rejected.

A grep of the installed jsonwebtoken@9.0.3 source shows no crit handling at all — the field is silently ignored during verification. As a result, a signer who relies on crit to mandate that verifiers respect an extension (e.g. a key-binding or policy header) gets no enforcement when the verifier is jsonwebtoken, even though the signer marked the extension as critical.

Reproduction

// npm i jsonwebtoken@9.0.3
const jwt = require('jsonwebtoken');
const crypto = require('crypto');

const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
const pubPem  = publicKey.export ({ type: 'spki',  format: 'pem' });
const privPem = privateKey.export({ type: 'pkcs8', format: 'pem' });

// Sign a token that declares an extension param as critical
const token = jwt.sign(
  { sub: 'x', role: 'admin' },
  privPem,
  { algorithm: 'RS256',
    header: { crit: ['x-attack-vector'], 'x-attack-vector': true } });

// Verify — RFC 7515 §4.1.11 requires rejection because jsonwebtoken does not
// understand or process the 'x-attack-vector' extension.
console.log(jwt.verify(token, pubPem, { algorithms: ['RS256'] }));

Observed output:

{ sub: 'x', role: 'admin', iat: 1779849601 }

Expected: a verification error (e.g. JsonWebTokenError: critical header parameter 'x-attack-vector' is not understood).

Cross-library comparison

jose@5.10.0 rejects the same token, citing the same RFC clause:

ERR_JOSE_NOT_SUPPORTED: Extension Header Parameter "x-attack-vector" is not recognized

So a deployment that issues tokens with crit-protected extensions via one library and verifies them with jsonwebtoken loses the protection the signer intended.

Why it matters

crit is the mechanism JWS provides for a signer to require that a particular extension header be honored. Concrete examples in the wild:

  • b64 (RFC 7797) — signer asserts the payload is not base64url-encoded; a verifier ignoring crit will treat it as encoded and either fail or, worse, validate a different payload than the one signed.
  • Custom security-policy extensions (e.g. token-binding hints, audience-restriction extensions, replay-protection nonces) where the signer explicitly demands enforcement.

A verifier that silently strips crit defeats the whole purpose of the field as defined in RFC 7515.

Suggested fix direction

In verify.js, after the header is decoded:

  1. If header.crit exists, it must be a non-empty array of strings (per §4.1.11).
  2. Reject if crit lists any name that this library does not implement. Since jsonwebtoken does not implement any crit extensions, the simplest correct behavior is to reject any token whose crit is non-empty unless the caller opts in via an option like crit: ['name1', ...] enumerating extensions the caller has handled out-of-band.

jose's implementation is a small reference.

Environment

  • jsonwebtoken 9.0.3 (current latest on npm)
  • Node v26.0.0
  • macOS 14

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions