ProvenPin Install

Express 5 + Passport JWT strategy: working versions (tested 2026-10-15)

Protect Express 5 routes with passport-jwt (bearer tokens signed by jsonwebtoken), stateless sessions and role checks.

passed3 passed · 0 failed2026-10-15 13:28 UTC
Status
passed
Last passed
2026-10-15 13:28 UTC
Tests
3 / 3
Language
TypeScript
Runtime
node v22.23.3
pinned versions12
express@5.3.0passport@0.7.0passport-jwt@4.0.1jsonwebtoken@9.0.3tsx@4.23.15@types/node@24.19.2supertest@7.3.1@types/supertest@7.2.1@types/express@5.0.6@types/passport@1.0.17@types/passport-jwt@4.0.1@types/jsonwebtoken@9.0.10
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 38742087302

The code

Exactly the files that passed. Your agent can pull them with get_recipe("express-passport-jwt") (install), or see them on GitHub.

package.json
{
  "name": "express-passport-jwt",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "express": "5.3.0",
    "passport": "0.7.0",
    "passport-jwt": "4.0.1",
    "jsonwebtoken": "9.0.3"
  },
  "devDependencies": {
    "tsx": "4.23.15",
    "@types/node": "24.19.2",
    "supertest": "7.3.1",
    "@types/supertest": "7.2.1",
    "@types/express": "5.0.6",
    "@types/passport": "1.0.17",
    "@types/passport-jwt": "4.0.1",
    "@types/jsonwebtoken": "9.0.10"
  }
}
src/app.ts
import express, { type NextFunction, type Request, type Response } from "express";
import passport from "passport";
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
import jwt from "jsonwebtoken";

type User = { id: string; role: "admin" | "user" };

export function createApp(secret: string) {
  passport.use(new JwtStrategy(
    { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: secret, algorithms: ["HS256"] },
    (payload: { sub: string; role: User["role"] }, done) => done(null, { id: payload.sub, role: payload.role }),
  ));
  const app = express();
  app.use(express.json());
  app.use(passport.initialize());
  const auth = passport.authenticate("jwt", { session: false });
  const requireRole = (role: User["role"]) => (req: Request, res: Response, next: NextFunction) =>
    (req.user as User).role === role ? next() : res.status(403).json({ error: "forbidden" });

  app.post("/login", (req, res) => {
    if (req.body?.password !== "secret") return res.status(401).json({ error: "invalid_credentials" });
    const role = req.body.username === "root" ? "admin" : "user";
    res.json({ token: jwt.sign({ role }, secret, { subject: req.body.username, expiresIn: "1h", algorithm: "HS256" }) });
  });
  app.get("/me", auth, (req, res) => { res.json(req.user); });
  app.get("/admin", auth, requireRole("admin"), (_req, res) => { res.json({ ok: true }); });
  return app;
}
test/app.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import request from "supertest";
import jwt from "jsonwebtoken";
import { createApp } from "../src/app.ts";

const app = createApp("test-secret");
const login = async (username: string) => (await request(app).post("/login").send({ username, password: "secret" })).body.token;

test("token unlocks /me", async () => {
  const res = await request(app).get("/me").set("authorization", `Bearer ${await login("ada")}`);
  assert.deepEqual(res.body, { id: "ada", role: "user" });
});

test("role check", async () => {
  assert.equal((await request(app).get("/admin").set("authorization", `Bearer ${await login("ada")}`)).status, 403);
  assert.equal((await request(app).get("/admin").set("authorization", `Bearer ${await login("root")}`)).status, 200);
});

test("rejects missing, forged and expired tokens", async () => {
  assert.equal((await request(app).get("/me")).status, 401);
  const forged = jwt.sign({ role: "admin" }, "other", { subject: "x" });
  assert.equal((await request(app).get("/me").set("authorization", `Bearer ${forged}`)).status, 401);
  const expired = jwt.sign({ role: "user", exp: Math.floor(Date.now() / 1000) - 10 }, "test-secret", { subject: "x" });
  assert.equal((await request(app).get("/me").set("authorization", `Bearer ${expired}`)).status, 401);
});

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.