Beispiel
jsonwebtoken 9.0.3: Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units
Verifiziertes Beispiel für npm jsonwebtoken 9.0.3: Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units. Der…
sha256:db08d34218a323680c118afa134f755bba43d7cabe50ec8738997d1c4a5b68f0
Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen.
Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen.
MIT-0
Ausführungsbelege
Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.
- Beleggrundlage
- Signierter Vertrag bestanden
- Verifizierungsbelege
- 2
- Signaturschlüssel, die es gebaut haben
- 2
Deklarierte Umgebung
node linux x64 node node npm
Umgebungen der Verifizierungsläufe
| Umgebung | Contract | Stufen | Lauf |
|---|---|---|---|
| node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-16 |
| node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-18 |
Fall
HOW- Ziel
- Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units
- Pakete
- Symbole
-
- jwt.sign
- jwt.verify
- Umgebung
- node
- Erstellt
- 2026-08-16T12:10:28Z
Contract
- assert jwt.verify returns payload claims directly at root and leaves .payload undefined unless complete: true is specified
- assert jwt.verify with complete: true returns an envelope containing header, payload, and signature
- assert jwt.sign throws a runtime error when registered claims like exp, aud, or iss exist in both payload and options
- assert numeric expiresIn is interpreted as relative duration in seconds rather than milliseconds or an absolute timestamp
- assert signing a string payload produces a raw string on verify and rejects expiresIn option
Dateien
- NOTES.md
- csx.json
- package-lock.json
- package.json
- src/index.mjs
- test/contract.mjs
Quelltext
# jsonwebtoken Verification Envelopes and Signing Claim Rules
## Prior Solutions
A search for `jsonwebtoken` returned existing published sample `sha256:83fd833c641d68652a876b9d95c6f97d220b969dc2a1f2cd2512294b6cfddbb6` ("Verify a JWT without accidentally trusting an unsigned token"), which covers pinning algorithms to reject unsigned tokens and prevent algorithm-confusion vulnerabilities. This sample investigates a different set of API traps: return value envelope shape differences between default and complete modes, registered claim duplication errors between payload objects and options, and duration unit interpretations in `jwt.sign`.
## Failure Mode
Code written assuming `jwt.verify` returns a nested envelope `{ payload, header }` fails loudly at runtime with `TypeError: Cannot read properties of undefined` when attempting to access `decoded.payload.sub`. Similarly, specifying registered claims like `exp` or `aud` in both the payload object and the options parameter fails loudly at runtime with `Error: Bad "options.expiresIn" option the payload already has an "exp" property.`
{"case":{"believed":"jwt.verify returns a decoded envelope containing payload and header properties by default, and jwt.sign merges payload registered claims with signing options.","caseId":"case:sha256:a1e359b04d2db6b1654df36689f3d6c8745619feac4aec9569a1338caa6eb5bb","contract":["assert jwt.verify returns payload claims directly at root and leaves .payload undefined unless complete: true is specified","assert jwt.verify with complete: true returns an envelope containing header, payload, and signature","assert jwt.sign throws a runtime error when registered claims like exp, aud, or iss exist in both payload and options","assert numeric expiresIn is interpreted as relative duration in seconds rather than milliseconds or an absolute timestamp","assert signing a string payload produces a raw string on verify and rejects expiresIn option"],"goal":"Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units","kind":"HOW","packages":["pkg:npm/jsonwebtoken@9.0.3"],"schemaVersion":1,"symbols":["jwt.sign","jwt.verify"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/jsonwebtoken@9.0.3"],"schemaVersion":1,"symbols":["jwt.sign","jwt.verify"],"verifierAdapter":"node-typescript@1"}
{
"name": "csx-sample-jsonwebtoken-payload-trap",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "csx-sample-jsonwebtoken-payload-trap",
"version": "1.0.0",
"license": "MIT-0",
"dependencies": {
"jsonwebtoken": "^9.0.2"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"license": "MIT",
"dependencies": {
"jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
}
}
}
{
"name": "jsonwebtoken-api-contract",
"version": "1.0.0",
"private": true,
"type": "module",
"license": "MIT-0",
"dependencies": {
"jsonwebtoken": "9.0.3"
}
}
import jwt from 'jsonwebtoken';
/**
* Signs a payload with HS256 algorithm.
* Demonstrates proper claim separation: registered claims should be passed via options
* or directly in the payload, but never duplicated across both.
*/
export function signToken(payload, secret, options = {}) {
return jwt.sign(payload, secret, {
algorithm: 'HS256',
...options
});
}
/**
* Verifies a token with HS256 algorithm.
* By default returns the decoded payload directly.
* When options.complete is true, returns { header, payload, signature }.
*/
export function verifyToken(token, secret, options = {}) {
return jwt.verify(token, secret, {
algorithms: ['HS256'],
...options
});
}
import { strict as assert } from 'node:assert';
import { signToken, verifyToken } from '../src/index.mjs';
const secret = 'super-secret-contract-key-2026';
// 1. jwt.verify returns the payload claims directly at root, not wrapped in a .payload object.
const tokenWithHeader = signToken(
{ sub: 'user-42', role: 'admin' },
secret,
{
expiresIn: '1h',
header: { kid: 'auth-key-1' }
}
);
const decodedDirect = verifyToken(tokenWithHeader, secret);
assert.equal(decodedDirect.sub, 'user-42');
assert.equal(decodedDirect.role, 'admin');
assert.equal(decodedDirect.payload, undefined);
assert.equal(decodedDirect.header, undefined);
// 2. Accessing the header or full envelope requires explicit { complete: true }.
const decodedComplete = verifyToken(tokenWithHeader, secret, { complete: true });
assert.equal(decodedComplete.header.kid, 'auth-key-1');
assert.equal(decodedComplete.header.alg, 'HS256');
assert.equal(decodedComplete.payload.sub, 'user-42');
assert.equal(typeof decodedComplete.signature, 'string');
// 3. Duplicate registered claims between payload and options throw fatal runtime errors.
assert.throws(
() => signToken({ sub: 'user-42', exp: Math.floor(Date.now() / 1000) + 3600 }, secret, { expiresIn: '1h' }),
/Bad "options\.expiresIn" option the payload already has an "exp" property\./
);
assert.throws(
() => signToken({ sub: 'user-42', aud: 'https://api.example.com' }, secret, { audience: 'https://api.example.com' }),
/Bad "options\.audience" option\. The payload already has an "aud" property\./
);
assert.throws(
() => signToken({ sub: 'user-42', iss: 'https://auth.example.com' }, secret, { issuer: 'https://auth.example.com' }),
/Bad "options\.issuer" option\. The payload already has an "iss" property\./
);
// 4. Numeric expiresIn is relative seconds from issued-at, not milliseconds or a unix timestamp.
const numericTtlToken = signToken({ sub: 'user-42' }, secret, { expiresIn: 120 });
const decodedNumeric = verifyToken(numericTtlToken, secret);
assert.equal(decodedNumeric.exp - decodedNumeric.iat, 120);
// 5. String payloads are signed as raw data: they reject expiresIn and verify back to a raw string primitive.
const rawStringToken = signToken('plain-text-payload', secret);
const decodedRawString = verifyToken(rawStringToken, secret);
assert.equal(typeof decodedRawString, 'string');
assert.equal(decodedRawString, 'plain-text-payload');
assert.throws(
() => signToken('plain-text-payload', secret, { expiresIn: '1h' }),
/invalid expiresIn option for string payload/
);
console.log('CONTRACT PASS: jsonwebtoken verify returns payload directly, rejects duplicate claims, and handles duration units');