Sessions with koa-session

Server-Side Sessions, app.keys, and Login/Logout Flow

Sessions with koa-session

koa-session adds encrypted, signed cookie-backed sessions to Koa. Learn how to configure app.keys, choose a session store, and implement a complete login and logout flow.

4 min read Level 2/5 #koa#auth#sessions
What you'll learn
  • Configure koa-session with app.keys for signed cookies
  • Swap the default memory store for a Redis-backed store
  • Implement login, authenticated routes, and logout

koa-session stores session data on the server and places only a session ID in a signed cookie on the client. The cookie cannot be tampered with because Koa’s built-in cookie signing uses HMAC-SHA256 with your app.keys.

Installation

npm install koa-session

Basic Setup

app.keys is a required prerequisite for signed cookies and sessions. Provide at least two keys so you can rotate them without immediately invalidating existing sessions.

import Koa from "koa";
import session from "koa-session";

const app = new Koa();

// Rotate keys: the first key is used to sign; all keys are used to verify.
app.keys = [process.env.SESSION_KEY_1, process.env.SESSION_KEY_2];

const SESSION_CONFIG = {
  key: "koa.sess",       // cookie name
  maxAge: 86_400_000,    // 1 day in ms
  httpOnly: true,        // JS cannot access the cookie
  secure: process.env.NODE_ENV === "production",
  sameSite: "lax",       // CSRF protection
  signed: true,          // HMAC signature (requires app.keys)
  rolling: false,        // don't reset maxAge on every request
};

app.use(session(SESSION_CONFIG, app));

Login and Logout

import Router from "@koa/router";
import bcrypt from "bcryptjs";
import { findUserByEmail } from "./db.js";

const router = new Router();

router.post("/login", async (ctx) => {
  const { email, password } = ctx.request.body;
  const user = await findUserByEmail(email);

  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    ctx.status = 401;
    ctx.body = { error: "Invalid credentials" };
    return;
  }

  // Store only a minimal, non-sensitive payload in the session.
  ctx.session.userId = user.id;
  ctx.session.role = user.role;
  ctx.body = { ok: true };
});

router.post("/logout", async (ctx) => {
  ctx.session = null; // clears the session and the cookie
  ctx.body = { ok: true };
});

Protecting Routes

async function requireAuth(ctx, next) {
  if (!ctx.session.userId) {
    ctx.status = 401;
    ctx.body = { error: "Not authenticated" };
    return;
  }
  await next();
}

router.get("/dashboard", requireAuth, async (ctx) => {
  ctx.body = { userId: ctx.session.userId };
});

Store Options

StorePackageNotes
Memory (default)built-inDev only — lost on restart
Rediskoa-redisProduction standard
PostgreSQLcustom get/set/destroyFits existing DB setups

Pass a custom store as store in the session config. The store must implement get(key, maxAge, { ctx }), set(key, sess, maxAge, { ctx }), and destroy(key, { ctx }).

Up Next

Learn how to use raw signed cookies — useful for lightweight state that doesn’t need a server-side store.

Cookies in Koa →