ProvenPin Install

bcryptjs password hashing + jose JWT sessions: working versions (tested 2026-10-15)

Hash passwords with bcryptjs and issue and verify signed JWT session tokens with jose. Framework-agnostic auth core.

passed3 passed · 0 failed2026-10-15 13:05 UTC
Status
passed
Last passed
2026-10-15 13:05 UTC
Tests
3 / 3
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
bcryptjs@3.0.3jose@6.2.12tsx@4.23.15@types/node@24.19.2
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 38738874208

The code

Exactly the files that passed. Your agent can pull them with get_recipe("bcryptjs-jose-sessions") (install), or see them on GitHub.

package.json
{
  "name": "bcryptjs-jose-sessions",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "bcryptjs": "3.0.3",
    "jose": "6.2.12"
  },
  "devDependencies": {
    "tsx": "4.23.15",
    "@types/node": "24.19.2"
  }
}
src/auth.ts
import bcrypt from "bcryptjs";
import { SignJWT, jwtVerify } from "jose";

export const hashPassword = (pw: string) => bcrypt.hash(pw, 10);
export const checkPassword = (pw: string, hash: string) => bcrypt.compare(pw, hash);

export async function createSession(userId: string, secret: Uint8Array, ttl = "2h") {
  return new SignJWT({ role: "user" })
    .setProtectedHeader({ alg: "HS256" })
    .setSubject(userId)
    .setIssuedAt()
    .setIssuer("my-app")
    .setExpirationTime(ttl)
    .sign(secret);
}

export async function readSession(token: string, secret: Uint8Array) {
  const { payload } = await jwtVerify(token, secret, { issuer: "my-app", algorithms: ["HS256"] });
  return { userId: payload.sub!, role: payload.role as string };
}
test/auth.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { hashPassword, checkPassword, createSession, readSession } from "../src/auth.ts";

const secret = new TextEncoder().encode("a-32-byte-long-test-secret-value!");

test("hash and verify a password", async () => {
  const h = await hashPassword("hunter2");
  assert.notEqual(h, "hunter2");
  assert.equal(await checkPassword("hunter2", h), true);
  assert.equal(await checkPassword("wrong", h), false);
});

test("session round trip", async () => {
  const t = await createSession("u1", secret);
  assert.deepEqual(await readSession(t, secret), { userId: "u1", role: "user" });
});

test("rejects tampered and expired tokens", async () => {
  const t = await createSession("u1", secret);
  await assert.rejects(readSession(t, new TextEncoder().encode("another-32-byte-long-secret-val!!")));
  const old = await createSession("u1", secret, "-1s");
  await assert.rejects(readSession(old, secret));
});

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.