ProvenPin Install

jsonwebtoken HS256 and RS256: working versions (tested 2026-10-15)

Sign and verify JSON Web Tokens with jsonwebtoken: HS256 secrets plus RS256 RSA keys from node:crypto, with deterministic expiry via clockTimestamp, issuer and audience checks, and rejection of the none algorithm.

passed8 passed · 0 failed2026-10-15 22:58 UTC
Status
passed
Last passed
2026-10-15 22:58 UTC
Tests
8 / 8
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
jsonwebtoken@9.0.3@types/jsonwebtoken@9.0.10@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38820570375

The code

Exactly the files that passed. Your agent can pull them with get_recipe("jsonwebtoken-hs256-rs256") (install), or see them on GitHub.

package.json
{
  "name": "jsonwebtoken-hs256-rs256",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "jsonwebtoken": "9.0.3"
  },
  "devDependencies": {
    "@types/jsonwebtoken": "9.0.10",
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/tokens.ts
import jwt from "jsonwebtoken";

export const HS_SECRET = "provenpin-hs256-secret";
export const ISSUER = "https://issuer.provenpin.dev";
export const AUDIENCE = "provenpin-api";

export type Payload = { sub: string } & Record<string, unknown>;

export type VerifyOptions = {
  issuer?: string;
  audience?: string;
  clockTimestamp?: number;
};

export function signHs256(payload: Payload, expiresIn: number | string = "1h"): string {
  return jwt.sign(payload, HS_SECRET, {
    algorithm: "HS256",
    expiresIn,
    issuer: ISSUER,
    audience: AUDIENCE,
  });
}

export function verifyHs256(token: string, options: VerifyOptions = {}): Payload {
  return jwt.verify(token, HS_SECRET, {
    algorithms: ["HS256"],
    issuer: options.issuer ?? ISSUER,
    audience: options.audience ?? AUDIENCE,
    clockTimestamp: options.clockTimestamp,
  }) as Payload;
}

export function signRs256(
  payload: Payload,
  privateKey: string,
  expiresIn: number | string = "1h",
): string {
  return jwt.sign(payload, privateKey, {
    algorithm: "RS256",
    expiresIn,
    issuer: ISSUER,
    audience: AUDIENCE,
  });
}

export function verifyRs256(
  token: string,
  publicKey: string,
  options: VerifyOptions = {},
): Payload {
  return jwt.verify(token, publicKey, {
    algorithms: ["RS256"],
    issuer: options.issuer ?? ISSUER,
    audience: options.audience ?? AUDIENCE,
    clockTimestamp: options.clockTimestamp,
  }) as Payload;
}
test/tokens.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { generateKeyPairSync } from "node:crypto";
import jwt from "jsonwebtoken";
import {
  AUDIENCE,
  ISSUER,
  signHs256,
  signRs256,
  verifyHs256,
  verifyRs256,
} from "../src/tokens.ts";

const { publicKey, privateKey } = generateKeyPairSync("rsa", {
  modulusLength: 2048,
  publicKeyEncoding: { type: "spki", format: "pem" },
  privateKeyEncoding: { type: "pkcs8", format: "pem" },
});

test("HS256: signs and verifies with expiresIn, issuer and audience", () => {
  const token = signHs256({ sub: "user-1" });
  const decoded = jwt.decode(token, { complete: true }) as {
    header: { alg: string };
    payload: { sub: string; iss: string; aud: string; iat: number; exp: number };
  };

  assert.equal(decoded.header.alg, "HS256");
  assert.equal(decoded.payload.iss, ISSUER);
  assert.equal(decoded.payload.aud, AUDIENCE);
  assert.equal(decoded.payload.exp - decoded.payload.iat, 3600);

  assert.equal(verifyHs256(token).sub, "user-1");
});

test("HS256: rejects a token issued for a different issuer", () => {
  const token = signHs256({ sub: "user-iss" });
  assert.throws(
    () => verifyHs256(token, { issuer: "https://evil.example" }),
    (err: unknown) => {
      assert.ok(err instanceof jwt.JsonWebTokenError);
      assert.match((err as Error).message, /issuer/);
      return true;
    },
  );
});

test("HS256: rejects a token presented to the wrong audience", () => {
  const token = signHs256({ sub: "user-aud" });
  assert.throws(
    () => verifyHs256(token, { audience: "someone-else" }),
    (err: unknown) => {
      assert.ok(err instanceof jwt.JsonWebTokenError);
      assert.match((err as Error).message, /audience/);
      return true;
    },
  );
});

test("HS256: detects TokenExpiredError deterministically via clockTimestamp", () => {
  const issuedAt = 1_700_000_000;
  const token = signHs256({ sub: "user-exp", iat: issuedAt }, 60);

  assert.equal(verifyHs256(token, { clockTimestamp: issuedAt }).sub, "user-exp");

  assert.throws(
    () => verifyHs256(token, { clockTimestamp: issuedAt + 61 }),
    (err: unknown) => {
      assert.ok(err instanceof jwt.TokenExpiredError);
      assert.equal(err.expiredAt.getTime(), (issuedAt + 60) * 1000);
      return true;
    },
  );
});

test("HS256: rejects the 'none' algorithm", () => {
  const encode = (value: object) =>
    Buffer.from(JSON.stringify(value)).toString("base64url");
  const token = `${encode({ alg: "none", typ: "JWT" })}.${encode({
    sub: "attacker",
    iss: ISSUER,
    aud: AUDIENCE,
    exp: Math.floor(Date.now() / 1000) + 3600,
  })}.`;

  assert.throws(
    () => verifyHs256(token),
    (err: unknown) => {
      assert.ok(err instanceof jwt.JsonWebTokenError);
      assert.ok(!(err instanceof jwt.TokenExpiredError));
      return true;
    },
  );
});

test("RS256: signs with the private key and verifies with the public key", () => {
  const token = signRs256({ sub: "user-rs" }, privateKey);
  const decoded = jwt.decode(token, { complete: true }) as { header: { alg: string } };

  assert.equal(decoded.header.alg, "RS256");
  assert.equal(verifyRs256(token, publicKey).sub, "user-rs");
});

test("RS256: rejects an HS256 token presented as RS256", () => {
  const hsToken = signHs256({ sub: "user-hs" });
  assert.throws(
    () => verifyRs256(hsToken, publicKey),
    (err: unknown) => {
      assert.ok(err instanceof jwt.JsonWebTokenError);
      return true;
    },
  );
});

test("RS256: rejects a token signed by an unrelated key", () => {
  const other = generateKeyPairSync("rsa", {
    modulusLength: 2048,
    publicKeyEncoding: { type: "spki", format: "pem" },
    privateKeyEncoding: { type: "pkcs8", format: "pem" },
  });
  const token = signRs256({ sub: "user-other" }, other.privateKey);

  assert.throws(
    () => verifyRs256(token, publicKey),
    (err: unknown) => {
      assert.ok(err instanceof jwt.JsonWebTokenError);
      assert.match((err as Error).message, /invalid signature/);
      return true;
    },
  );
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.