ProvenPin Install

Express 5 + express-session login flow, e2e-style over real HTTP with a cookie jar: working versions (tested 2026-10-15)

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.

passed2 passed · 0 failed2026-10-15 13:29 UTC
Status
passed
Last passed
2026-10-15 13:29 UTC
Tests
2 / 2
Language
TypeScript
Runtime
node v22.23.3
pinned versions7
express@5.3.0express-session@1.19.0tsx@4.23.15@types/node@24.19.2fetch-cookie@3.2.0@types/express@5.0.6@types/express-session@1.19.0
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-session-cookie-e2e") (install), or see them on GitHub.

package.json
{
  "name": "express-session-cookie-e2e",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "express": "5.3.0",
    "express-session": "1.19.0"
  },
  "devDependencies": {
    "tsx": "4.23.15",
    "@types/node": "24.19.2",
    "fetch-cookie": "3.2.0",
    "@types/express": "5.0.6",
    "@types/express-session": "1.19.0"
  }
}
src/app.ts
import express from "express";
import session from "express-session";

declare module "express-session" { interface SessionData { user?: string } }

export function createApp(secret: string) {
  const app = express();
  app.use(express.urlencoded({ extended: false }));
  app.use(session({
    secret, resave: false, saveUninitialized: false, name: "sid",
    cookie: { httpOnly: true, sameSite: "lax", secure: false /* true behind HTTPS */, maxAge: 3600_000 },
  }));
  app.post("/login", (req, res, next) => {
    if (req.body.password !== "secret") return res.status(401).send("bad credentials");
    req.session.regenerate((err) => { // new session id on login prevents fixation
      if (err) return next(err);
      req.session.user = req.body.username;
      res.redirect(303, "/dashboard");
    });
  });
  app.get("/dashboard", (req, res) => {
    if (!req.session.user) return res.redirect(303, "/login");
    res.send(`Welcome ${req.session.user}`);
  });
  app.post("/logout", (req, res) => { req.session.destroy(() => res.clearCookie("sid").redirect(303, "/login")); });
  app.get("/login", (_req, res) => { res.send("login page"); });
  return app;
}
test/e2e.test.ts
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import type { Server } from "node:http";
import makeFetchCookie from "fetch-cookie";
import { createApp } from "../src/app.ts";

let server: Server, base: string;
before(() => new Promise<void>((r) => { server = createApp("test-secret").listen(0, () => { base = `http://127.0.0.1:${(server.address() as any).port}`; r(); }); }));
after(() => new Promise<void>((r) => server.close(() => r())));

test("full browser-like flow: login, dashboard, logout", async () => {
  const jar = new makeFetchCookie.toughCookie.CookieJar();
  const f = makeFetchCookie(fetch, jar);
  assert.equal(await (await f(`${base}/dashboard`)).text(), "login page"); // redirect followed
  const login = await f(`${base}/login`, { method: "POST", body: new URLSearchParams({ username: "ada", password: "secret" }) });
  assert.equal(await login.text(), "Welcome ada");
  const cookie = (await jar.getCookies(base))[0];
  assert.equal(cookie.key, "sid");
  assert.equal(cookie.httpOnly, true);
  await f(`${base}/logout`, { method: "POST" });
  assert.equal(await (await f(`${base}/dashboard`)).text(), "login page");
});

test("bad password is 401 and sets no session", async () => {
  const jar = new makeFetchCookie.toughCookie.CookieJar();
  const f = makeFetchCookie(fetch, jar);
  const r = await f(`${base}/login`, { method: "POST", body: new URLSearchParams({ username: "x", password: "nope" }) });
  assert.equal(r.status, 401);
  assert.equal((await jar.getCookies(base)).length, 0);
});

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.