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
npm install --no-audit --no-fund --ignore-scripts=false npm test
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.
{
"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"
}
}
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;
}
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;
}
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.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Express 5 + express-session login flow, e2e-style over real HTTP with a cookie jar
Session login/logout with express-session (regenerate on login, httpOnly cookies), tested end to end against a real listening server using fetch-cookie, no browser or Playwright.
Express 5 + Passport JWT strategy
Protect Express 5 routes with passport-jwt (bearer tokens signed by jsonwebtoken), stateless sessions and role checks.
cors express preflight
Configure cors with express to reflect allowed origins, answer OPTIONS preflight with Access-Control-Allow-* headers, and deny a disallowed origin, tested offline with supertest.
Express 5 + Helmet + express-rate-limit hardening
Add security headers with Helmet and per-IP rate limiting with express-rate-limit to an Express 5 API.
All Express recipesAll passport recipesAll passport-local recipesAll express-session recipes