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 });remember: false – session cookie
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 });
}Choosing between “unchecked = session cookie” and “unchecked = default”
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 cookieOption 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:
- A session payload is created with
{ uid, iat, exp }whereexp = iat + maxAge - The payload is encrypted using AES-256-CBC via iron-session
- The encrypted payload is set as a cookie:
remember: true– cookiemaxAgeis set torememberMaxAgeremember: false– no cookiemaxAge(session cookie)rememberomitted – cookiemaxAgeis set tomaxAge
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
httpOnlycookies (always forced on, preventing JavaScript access) andsecurecookies (enforced in production, preventing transmission over HTTP). -
Password changes: After a password change, all existing sessions remain valid until they expire. Use the
passwordChangedAtcheck 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 });
}