Skip to content

Remember Me

Implement persistent login sessions with ideal-auth.

Updated View as Markdown

ideal-auth supports three session duration modes controlled by the LoginOptions parameter. This guide explains how each mode works, how to implement a “Remember me” checkbox, and how to customize durations.

Three session modes

The remember option in LoginOptions controls session duration:

remember value Cookie behavior Default duration
true Persistent cookie with maxAge 30 days
false Session cookie (no maxAge) Until browser closes
undefined (omitted) Persistent cookie with maxAge 7 days
interface LoginOptions {
  remember?: boolean;
}

remember: true – 30-day session

The cookie is set with maxAge: 2592000 (30 days). The session persists across browser restarts.

await session.login(user, { remember: true });

The cookie is set without a maxAge, making it a session cookie. The browser deletes it when the user closes their browser.

await session.login(user, { remember: false });

No option (default) – 7-day session

When remember is not passed, the cookie is set with maxAge: 604800 (7 days).

await session.login(user);
// or equivalently:
await session.attempt(credentials);

Login form checkbox

The most common pattern is a checkbox on the login form that controls the remember option.

Client-side form

<form action="/api/auth/login" method="POST">
  <label>
    Email
    <input type="email" name="email" required />
  </label>
  <label>
    Password
    <input type="password" name="password" required />
  </label>
  <label>
    <input type="checkbox" name="remember" value="true" />
    Remember me
  </label>
  <button type="submit">Sign in</button>
</form>

Server-side handler

import { createAuth, createHash } from 'ideal-auth';

const hash = createHash();
const auth = createAuth({
  secret: process.env.IDEAL_AUTH_SECRET!,
  cookie: cookieBridge,
  hash,
  resolveUser: (id) => db.user.findUnique({ where: { id } }),
  resolveUserByCredentials: (creds) =>
    db.user.findUnique({ where: { email: creds.email } }),
});

// POST /api/auth/login
export async function login(req, res) {
  const { email, password, remember } = req.body;

  const session = auth();
  const success = await session.attempt(
    { email, password },
    { remember: remember === 'true' },
  );

  if (!success) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  return res.json({ success: true });
}

There are two common interpretations of an unchecked “Remember me” box:

Option A: Unchecked means session cookie (closes with browser)

const options = { remember: remember === 'true' };
// checked → { remember: true }  → 30 days
// unchecked → { remember: false } → session cookie

Option B: Unchecked means default duration

const options = remember === 'true' ? { remember: true } : {};
// checked → { remember: true }  → 30 days
// unchecked → {}                 → 7 days (default)

Option A is the traditional behavior. Option B is more user-friendly since sessions survive browser restarts even when the user forgets to check the box.

Configuring custom durations

Override the default durations in your createAuth config:

const auth = createAuth({
  secret: process.env.IDEAL_AUTH_SECRET!,
  cookie: cookieBridge,
  session: {
    maxAge: 60 * 60 * 24 * 3,      // 3 days (default, when remember is omitted)
    rememberMaxAge: 60 * 60 * 24 * 90, // 90 days (when remember: true)
  },
  resolveUser: (id) => db.user.findUnique({ where: { id } }),
  // ...
});
Config option Default Controls
session.maxAge 604800 (7 days) Duration when remember is omitted
session.rememberMaxAge 2592000 (30 days) Duration when remember: true

When remember: false, the cookie has no maxAge regardless of these settings.

How it works internally

When login() or attempt() is called:

  1. A session payload is created with { uid, iat, exp } where exp = iat + maxAge
  2. The payload is encrypted using AES-256-CBC via iron-session
  3. The encrypted payload is set as a cookie:
    • remember: true – cookie maxAge is set to rememberMaxAge
    • remember: false – no cookie maxAge (session cookie)
    • remember omitted – cookie maxAge is set to maxAge

The exp in the session payload always matches the cookie duration. Even if a session cookie somehow survives a browser restart (some browsers restore session cookies), the exp check in unseal will reject it once the intended duration has passed.

Security tradeoffs

Longer sessions are more convenient but carry more risk:

  • Shared or public computers: A 30-day session on a shared computer means the next person can access the account for a month. This is why “Remember me” should always be an explicit opt-in.

  • Stolen sessions: If a session cookie is compromised (via XSS, network interception, etc.), a longer session gives the attacker a larger window of access. ideal-auth mitigates this with httpOnly cookies (always forced on, preventing JavaScript access) and secure cookies (enforced in production, preventing transmission over HTTP).

  • Password changes: After a password change, all existing sessions remain valid until they expire. Use the passwordChangedAt check described in the Password Reset guide to invalidate old sessions immediately.

Complete example

import { createAuth, createHash } from 'ideal-auth';

const hash = createHash();

const auth = createAuth({
  secret: process.env.IDEAL_AUTH_SECRET!,
  cookie: cookieBridge,
  hash,
  session: {
    cookieName: 'session',
    maxAge: 60 * 60 * 24 * 7,        // 7 days
    rememberMaxAge: 60 * 60 * 24 * 30, // 30 days
    cookie: {
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      path: '/',
    },
  },
  resolveUser: (id) => db.user.findUnique({ where: { id } }),
  resolveUserByCredentials: (creds) =>
    db.user.findUnique({ where: { email: creds.email } }),
});

// Login with remember me support
export async function login(req, res) {
  const { email, password, remember } = req.body;

  const session = auth();
  const success = await session.attempt(
    { email, password },
    { remember: remember === 'true' },
  );

  if (!success) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  return res.json({ success: true });
}
Navigation

Type to search…

↑↓ navigate↵ selectEsc close