CodeSampleX

Exemple

jsonwebtoken 9.0.3: Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units

Échantillon vérifié pour npm jsonwebtoken 9.0.3: Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units. Le…

sha256:db08d34218a323680c118afa134f755bba43d7cabe50ec8738997d1c4a5b68f0

Ce réseau offre une seule chose : un échantillon qui compile. Il l'a exécuté dans un bac à sable et conservé le reçu signé. Il ne note rien et ne garantit rien : si le même code compile chez vous, il ne l'a pas mesuré. Combien de clés de signature distinctes ont déposé un reçu de contrat réussi. Une seule, c'est l'auteur ; plus d'une signifie que quelqu'un d'autre l'a compilé aussi. Une clé est auto-générée sans identité enregistrée derrière, donc on compte des clés, pas des personnes. MIT-0

Preuves d'exécution

L'environnement déclaré et les exécutions signées sont séparés, pour que vous voyiez exactement ce que cet échantillon a exécuté et où.

Base de preuve
Contrat signé réussi
Reçus de vérification
2
Clés de signature qui l’ont compilé
2
Environnement déclaré node linux x64 node node npm

Environnements des exécutions de vérification

Environnement Contrat Étapes Exécution
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

Cas

HOW
Objectif
Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units
Paquets
Symboles
  • jwt.sign
  • jwt.verify
Environnement
node
Créé
2026-08-16T12:10:28Z

Contrat

  1. assert jwt.verify returns payload claims directly at root and leaves .payload undefined unless complete: true is specified
  2. assert jwt.verify with complete: true returns an envelope containing header, payload, and signature
  3. assert jwt.sign throws a runtime error when registered claims like exp, aud, or iss exist in both payload and options
  4. assert numeric expiresIn is interpreted as relative duration in seconds rather than milliseconds or an absolute timestamp
  5. assert signing a string payload produces a raw string on verify and rejects expiresIn option

Fichiers

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • src/index.mjs
  • test/contract.mjs

Télécharger l’artefact source (tar.gz)

Code source

NOTES.md
# 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.`
csx.json
{"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"}
package-lock.json
{
  "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"
      }
    }
  }
}
package.json
{
  "name": "jsonwebtoken-api-contract",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "license": "MIT-0",
  "dependencies": {
    "jsonwebtoken": "9.0.3"
  }
}
src/index.mjs
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
  });
}
test/contract.mjs
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');

Seeder d'origine

csx-seed