# Data flow — auth0/node-jsonwebtoken

> Canonical page: https://codevetter.com/unpack/jsonwebtoken/data-flow

# Data flow — auth0/node-jsonwebtoken

Scanned commit b924272f29192e12926b5414546f7c5bfcc9579d (2026-06-25). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

There are exactly two input boundaries — the caller-supplied `(payload | jwtString, key, options)` tuple — and one output boundary, either a returned/thrown value or a `(err, result)` callback invocation. No state is owned across calls: the library is entirely stateless, with the only module-level mutable structures being the algorithm allow-list arrays built at require time. The central transform on both paths is key normalization: whatever the caller passes (string, Buffer, PEM, JWK-ish object) is funnelled through `crypto.createPrivateKey`/`createPublicKey` with a `createSecretKey` fallback into a `KeyObject`, and every downstream decision reads `key.type`, `key.asymmetricKeyType`, and `key.asymmetricKeyDetails` off that normalized object. Claim data flows one way on sign (options → payload properties → JWS) and one way on verify (JWS → decoded payload → claim assertions → returned payload).
- On sign, key material normalization is a try/catch cascade: if not already a `KeyObject`, attempt `createPrivateKey(secretOrPrivateKey)`, fall back to `createSecretKey(Buffer.from(...))` for strings, and on double failure emit `secretOrPrivateKey is not valid key material`. — [sign.js#L114-124](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/sign.js#L114-L124)
- Verify mirrors that cascade with `createPublicKey` then `createSecretKey`, producing `secretOrPublicKey is not valid key material` — this is what converts a hostile object with a throwing `toString` into a clean error instead of arbitrary execution. — [verify.js#L120-130](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L120-L130) [test/jwt.malicious.tests.js#L33-38](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/test/jwt.malicious.tests.js#L33-L38)
- The JWS header is assembled by `Object.assign` with a fixed precedence: computed `{alg, typ, kid}` first (alg defaults to `HS256`, `typ` is `'JWT'` only for object payloads, `kid` comes from `options.keyid`), then `options.header` spread last so it can override any of them including `alg`. — [sign.js#L94-101](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/sign.js#L94-L101)
- A single `timestamp` value — `payload.iat || Math.floor(Date.now()/1000)` — is computed once and is the base for both `nbf` and `exp`, guaranteeing the three time claims are internally consistent within one sign call. — [sign.js#L185-215](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/sign.js#L185-L215)
- On verify, the token is parsed twice: once shallowly as `jwtString.split('.')` for arity (`jwt malformed` when length !== 3) and signature-presence checks on `parts[2]`, and once fully through `decode(jwtString, {complete: true})`. — [verify.js#L67-83](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L67-L83) [verify.js#L106](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L106)
- `options` is defensively shallow-cloned on entry to verify (`Object.assign({}, options)`) specifically because the function later writes `options.algorithms` when the caller omitted it — caller-supplied objects are never mutated. — [verify.js#L31-32](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L31-L32) [verify.js#L132-142](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L132-L142)
- When `options.algorithms` is absent, the allow-list is derived from the normalized key: `secret`→HS_ALGS, `rsa`/`rsa-pss`→RSA_KEY_ALGS, `ec`→EC_KEY_ALGS, everything else→PUB_KEY_ALGS. This binds the accepted alg set to the key material, not to the token. — [verify.js#L132-142](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L132-L142) [README.md#L146-150](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/README.md#L146-L150)
- The token-supplied `header.alg` flows into `jws.verify(jwtString, decodedToken.header.alg, secretOrPublicKey)`, but only after passing the allow-list membership check and the key-type compatibility check — the ordering at lines 144→152→165 is the whole defense. — [verify.js#L144-165](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L144-L165)
- The verified `payload` object returned to the caller is the same object produced by `decode`, not a copy — all seven claim checks read from it without transforming it, so callers receive attacker-influenced data verbatim (the README explicitly warns to sanitize it). — [verify.js#L174](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L174) [verify.js#L251-261](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L251-L261) [README.md#L132](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/README.md#L132)
- Both functions funnel every error through a single sink — `failure(err)` in sign and `done(err, data)` in verify — which dispatches to `callback(err)` when async or `throw err` when sync, so sync and async modes are guaranteed to produce identical errors. — [sign.js#L103-108](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/sign.js#L103-L108) [verify.js#L34-43](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L34-L43) [test/test-utils.js#L28-37](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/test/test-utils.js#L28-L37)
- `decode` normalizes the payload channel: `jws.decode` may return a string payload, which `decode` opportunistically `JSON.parse`s and keeps only if the result is a non-null object — otherwise the string passes through unchanged. — [decode.js#L7-17](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/decode.js#L7-L17)
- `timespan(time, iat)` is the shared conversion from relative to absolute time on both paths: it feeds `payload.nbf`/`payload.exp` on sign and `maxAgeTimestamp` on verify, using `ms()` for strings and plain addition for numbers. — [lib/timespan.js#L3-17](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/lib/timespan.js#L3-L17) [sign.js#L195](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/sign.js#L195) [sign.js#L207](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/sign.js#L207) [verify.js#L242](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/verify.js#L242)
- The only file-system reads in the entire repo are in tests, loading PEM fixtures from `test/*.pem` — the library itself performs zero file or network I/O. — [test/jwt.asymmetric_signing.tests.js#L10-12](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/test/jwt.asymmetric_signing.tests.js#L10-L12) [test/schema.tests.js#L9-12](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/test/schema.tests.js#L9-L12) [test/verify.tests.js#L12-13](https://github.com/auth0/node-jsonwebtoken/blob/b924272f29192e12926b5414546f7c5bfcc9579d/test/verify.tests.js#L12-L13)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
