ProvenPin Install

Better Auth email/password on in-memory SQLite: working versions (tested 2026-10-15)

Configure Better Auth with better-sqlite3 in memory, run its migrations, then sign up, sign in through auth.handler with a session cookie, read the session, reject bad credentials and sign out, all offline.

passed5 passed · 0 failed2026-10-15 20:08 UTC
Status
passed
Last passed
2026-10-15 20:08 UTC
Tests
5 / 5
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
better-auth@1.7.10better-sqlite3@13.0.3@types/node@26.6.5tsx@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 38799498972

The code

Exactly the files that passed. Your agent can pull them with get_recipe("better-auth-sqlite-email-password") (install), or see them on GitHub.

package.json
{
  "name": "better-auth-sqlite-email-password",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "better-auth": "1.7.10",
    "better-sqlite3": "13.0.3"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/auth.ts
import { betterAuth } from "better-auth";
import { getMigrations } from "better-auth/db/migration"; // 1.7 moved it off better-auth/db
import Database from "better-sqlite3";

/** Better Auth on an in-memory SQLite database with email/password enabled. */
export async function makeAuth() {
  const auth = betterAuth({
    database: new Database(":memory:"),
    secret: "test-secret-test-secret-test-secret-0123",
    baseURL: "http://localhost:3000",
    emailAndPassword: { enabled: true, minPasswordLength: 10 },
    telemetry: { enabled: false },
  });
  const { runMigrations } = await getMigrations(auth.options);
  await runMigrations(); // creates user/session/account/verification tables
  return auth;
}

export type Auth = Awaited<ReturnType<typeof makeAuth>>;
test/auth.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { makeAuth } from "../src/auth.ts";

const creds = { email: "[email protected]", password: "correct-horse-battery", name: "JP" };

test("sign up creates a user and a session", async () => {
  const auth = await makeAuth();
  const r = await auth.api.signUpEmail({ body: creds });
  assert.equal(r.user.email, "[email protected]");
  assert.equal(r.user.name, "JP");
  assert.equal(r.user.emailVerified, false);
  assert.equal(typeof r.token, "string");
});

test("duplicate email and short password are rejected", async () => {
  const auth = await makeAuth();
  await auth.api.signUpEmail({ body: creds });
  await assert.rejects(auth.api.signUpEmail({ body: creds }), (e: { status?: string; statusCode?: number }) => e.statusCode === 422 || e.status === "UNPROCESSABLE_ENTITY");
  await assert.rejects(auth.api.signUpEmail({ body: { ...creds, email: "[email protected]", password: "short" } }), (e: { statusCode?: number }) => e.statusCode === 400);
});

test("sign in over HTTP returns a session cookie usable for getSession", async () => {
  const auth = await makeAuth();
  await auth.api.signUpEmail({ body: creds });
  const res = await auth.handler(new Request("http://localhost:3000/api/auth/sign-in/email", {
    method: "POST",
    headers: { "content-type": "application/json", origin: "http://localhost:3000" },
    body: JSON.stringify({ email: creds.email, password: creds.password }),
  }));
  assert.equal(res.status, 200);
  const setCookie = res.headers.get("set-cookie") ?? "";
  assert.match(setCookie, /better-auth\.session_token=/);
  assert.match(setCookie, /HttpOnly/i);
  const cookie = setCookie.split(";")[0];
  const session = await auth.api.getSession({ headers: new Headers({ cookie }) });
  assert.equal(session?.user.email, creds.email);
});

test("wrong password is 401 and no session without cookie", async () => {
  const auth = await makeAuth();
  await auth.api.signUpEmail({ body: creds });
  const res = await auth.handler(new Request("http://localhost:3000/api/auth/sign-in/email", {
    method: "POST",
    headers: { "content-type": "application/json", origin: "http://localhost:3000" },
    body: JSON.stringify({ email: creds.email, password: "wrong-password-123" }),
  }));
  assert.equal(res.status, 401);
  assert.equal(await auth.api.getSession({ headers: new Headers() }), null);
});

test("sign out revokes the session", async () => {
  const auth = await makeAuth();
  const { headers } = await auth.api.signUpEmail({ body: creds, returnHeaders: true });
  const cookie = (headers.get("set-cookie") ?? "").split(";")[0];
  const h = new Headers({ cookie });
  assert.ok(await auth.api.getSession({ headers: h }));
  await auth.api.signOut({ headers: h });
  assert.equal(await auth.api.getSession({ headers: h }), null);
});

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.