ProvenPin Install

passport-local express session auth: working versions (tested 2026-10-17)

Authenticate express requests with passport LocalStrategy against an in-memory store, persist the login with express-session and guard a protected route offline.

passed3 passed · 0 failed2026-10-17 05:58 UTC
Status
passed
Last passed
2026-10-17 05:58 UTC
Tests
3 / 3
Language
TypeScript
Runtime
node v22.23.3
pinned versions12
express@5.3.0express-session@1.19.0passport@0.7.0passport-local@1.0.0supertest@7.3.1@types/express@5.0.6@types/express-session@1.19.0@types/node@26.6.5@types/passport@1.0.17@types/passport-local@1.0.38@types/supertest@7.2.1tsx@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 39008163431

The code

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

package.json
{
  "name": "passport-local-express-session",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "express": "5.3.0",
    "express-session": "1.19.0",
    "passport": "0.7.0",
    "passport-local": "1.0.0"
  },
  "devDependencies": {
    "@types/express": "5.0.6",
    "@types/express-session": "1.19.0",
    "@types/node": "26.6.5",
    "@types/passport": "1.0.17",
    "@types/passport-local": "1.0.38",
    "@types/supertest": "7.2.1",
    "supertest": "7.3.1",
    "tsx": "4.23.15"
  }
}
src/auth.ts
import passport from "passport";
import { Strategy as LocalStrategy } from "passport-local";

export type User = {
  id: string;
  username: string;
  password: string;
};

export type PublicUser = {
  id: string;
  username: string;
};

const users = new Map<string, User>([
  ["alice", { id: "1", username: "alice", password: "secret" }],
]);

export function findUser(username: string): User | undefined {
  return users.get(username);
}

export function findUserById(id: string): User | undefined {
  for (const user of users.values()) {
    if (user.id === id) return user;
  }
  return undefined;
}

export function toPublicUser(user: User): PublicUser {
  return { id: user.id, username: user.username };
}

export function configurePassport(): typeof passport {
  passport.use(
    new LocalStrategy((username, password, done) => {
      const user = findUser(username);
      if (!user || user.password !== password) {
        done(null, false, { message: "invalid credentials" });
        return;
      }
      done(null, user);
    }),
  );

  passport.serializeUser((user, done) => {
    done(null, (user as User).id);
  });

  passport.deserializeUser((id, done) => {
    done(null, findUserById(id as string) ?? null);
  });

  return passport;
}
src/app.ts
import express from "express";
import session from "express-session";
import passport from "passport";
import { configurePassport, toPublicUser, type User } from "./auth.ts";

export function createApp(): express.Express {
  configurePassport();

  const app = express();

  app.use(express.json());
  app.use(express.urlencoded({ extended: false }));
  app.use(
    session({
      secret: "recipe-session-secret",
      resave: false,
      saveUninitialized: false,
    }),
  );
  app.use(passport.initialize());
  app.use(passport.session());

  app.post("/login", (req, res, next) => {
    passport.authenticate(
      "local",
      (err: unknown, user: User | false, info?: { message?: string }) => {
        if (err) {
          next(err);
          return;
        }
        if (!user) {
          res.status(401).json({ error: info?.message ?? "unauthorized" });
          return;
        }
        req.logIn(user, (loginError) => {
          if (loginError) {
            next(loginError);
            return;
          }
          res.json({ user: toPublicUser(user) });
        });
      },
    )(req, res, next);
  });

  app.get("/me", (req, res) => {
    const user = req.user as User | undefined;
    if (!req.isAuthenticated() || !user) {
      res.status(401).json({ error: "unauthorized" });
      return;
    }
    res.json({ user: toPublicUser(user) });
  });

  app.post("/logout", (req, res, next) => {
    req.logout((err) => {
      if (err) {
        next(err);
        return;
      }
      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 { createApp } from "../src/app.ts";

test("rejects a bad password", async () => {
  const agent = request.agent(createApp());
  const res = await agent
    .post("/login")
    .type("form")
    .send({ username: "alice", password: "wrong" });

  assert.equal(res.status, 401);
  assert.equal(res.body.error, "invalid credentials");
});

test("login sets a session and the protected route returns the user", async () => {
  const agent = request.agent(createApp());

  const login = await agent
    .post("/login")
    .type("form")
    .send({ username: "alice", password: "secret" });

  assert.equal(login.status, 200);
  assert.equal(login.body.user.username, "alice");
  assert.equal(login.body.user.password, undefined);
  assert.ok(login.headers["set-cookie"]);

  const me = await agent.get("/me");
  assert.equal(me.status, 200);
  assert.equal(me.body.user.username, "alice");
});

test("protected route rejects anonymous requests", async () => {
  const res = await request(createApp()).get("/me");
  assert.equal(res.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.