CodeSampleX

Пример

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

Проверенный пример — npm jsonwebtoken 9.0.3: Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units. Контракт…

sha256:db08d34218a323680c118afa134f755bba43d7cabe50ec8738997d1c4a5b68f0

Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла. Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди. MIT-0

Свидетельства выполнения

Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.

Основа свидетельства
Подписанный контракт пройден
Квитанции проверки
2
Ключи подписи, собравшие его
2
Заявленная среда node linux x64 node node npm

Среды запусков проверки

Окружение Контракт Этапы Запуск
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

Кейс

HOW
Цель
Handle jsonwebtoken verify return envelope shapes, sign claim option collisions, and duration units
Пакеты
Символы
  • jwt.sign
  • jwt.verify
Окружение
node
Создан
2026-08-16T12:10:28Z

Контракт

  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

Файлы

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

Скачать артефакт с исходным кодом (tar.gz)

Исходный код

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');

Исходный сидер

csx-seed