Configuration
Environment variables
Section titled “Environment variables”| Variable | Description | Required |
|---|---|---|
IDEAL_AUTH_SECRET | Encryption secret for sessions and token signing. Must be at least 32 characters. Generate one with bunx ideal-auth secret. | Yes |
createAuth
Section titled “createAuth”createAuth is the main entry point for session authentication. It returns a factory function that produces a request-scoped AuthInstance.
import { createAuth, createHash } from 'ideal-auth';
const hash = createHash();
const auth = createAuth({ secret: process.env.IDEAL_AUTH_SECRET!, cookie: cookieBridge, session: { cookieName: 'my_session', maxAge: 60 * 60 * 24, // 1 day rememberMaxAge: 60 * 60 * 24 * 90, // 90 days cookie: { sameSite: 'strict', domain: '.example.com', }, }, resolveUser: async (id) => db.user.findUnique({ where: { id } }), resolveUserByCredentials: async (creds) => db.user.findUnique({ where: { email: creds.email } }), hash, credentialKey: 'password', passwordField: 'password',});Config options
Section titled “Config options”| Option | Type | Default | Description |
|---|---|---|---|
secret | string | — | Required. Encryption secret, at least 32 characters. Typically process.env.IDEAL_AUTH_SECRET. |
cookie | CookieBridge | — | Required. Object with get, set, and delete methods for your framework’s cookie API. See Getting Started. |
session | object | See below | Session configuration (cookie name, TTL, cookie options). |
resolveUser | (id: string) => Promise<TUser | null> | — | Required. Looks up a user by ID. Called when rehydrating a session from an encrypted cookie. |
resolveUserByCredentials | (credentials: Record<string, any>) => Promise<TUser | null> | undefined | Looks up a user by credentials (with the password field stripped). Used by attempt() in the Laravel-style flow. |
hash | HashInstance | undefined | A createHash() instance. Required if using resolveUserByCredentials with attempt(). |
credentialKey | string | 'password' | The key in the credentials object that holds the plaintext password. attempt() strips this key before passing credentials to resolveUserByCredentials. |
passwordField | string | 'password' | The field on the user object that holds the stored bcrypt hash. Used by attempt() to verify the password. |
attemptUser | (credentials: Record<string, any>) => Promise<TUser | null> | undefined | Escape hatch for full control over credential verification. If provided, attempt() delegates entirely to this function instead of using hash + resolveUserByCredentials. |
CookieBridge interface
Section titled “CookieBridge interface”The cookie bridge decouples ideal-auth from any specific framework. It has three methods:
interface CookieBridge { get(name: string): Promise<string | undefined> | string | undefined; set(name: string, value: string, options: CookieOptions): Promise<void> | void; delete(name: string): Promise<void> | void;}All three methods can be synchronous or asynchronous.
attempt() strategies
Section titled “attempt() strategies”There are two ways to configure attempt():
Laravel-style (recommended): Provide hash, resolveUserByCredentials, and optionally credentialKey and passwordField. The attempt() method will strip the password from credentials, look up the user, and verify the hash automatically.
const auth = createAuth({ // ... hash: createHash(), resolveUserByCredentials: async (creds) => db.user.findUnique({ where: { email: creds.email } }), credentialKey: 'password', // default passwordField: 'password', // default});
// Usage: auth().attempt({ email, password })Escape hatch: Provide attemptUser for complete control. This takes precedence over the Laravel-style config.
const auth = createAuth({ // ... attemptUser: async (credentials) => { const user = await db.user.findUnique({ where: { email: credentials.email }, }); if (!user) return null; const valid = await customVerify(credentials.password, user.hash); return valid ? user : null; },});Session config
Section titled “Session config”The session object on createAuth controls cookie naming, TTL, and cookie attributes.
| Option | Type | Default | Description |
|---|---|---|---|
cookieName | string | 'ideal_session' | Name of the session cookie. |
maxAge | number | 604800 (7 days) | Session lifetime in seconds. Used when remember is not set or remember: false. |
rememberMaxAge | number | 2592000 (30 days) | Extended session lifetime in seconds. Used when login(user, { remember: true }) is called. |
cookie | Partial<ConfigurableCookieOptions> | {} | Additional cookie attributes (see table below). |
Cookie options
Section titled “Cookie options”The session.cookie object accepts any cookie attribute except httpOnly, which is always forced to true and cannot be overridden.
| Option | Type | Default | Description |
|---|---|---|---|
secure | boolean | true in production | Whether the cookie is sent only over HTTPS. Defaults to true when NODE_ENV === 'production'. Can be overridden. |
sameSite | 'lax' | 'strict' | 'none' | 'lax' | SameSite attribute for CSRF protection. |
path | string | '/' | URL path scope for the cookie. |
domain | string | undefined | Domain scope for the cookie. |
Session payload
Section titled “Session payload”The encrypted session cookie contains a SessionPayload:
interface SessionPayload { uid: string; // User ID (always stored as string) iat: number; // Issued-at timestamp (Unix seconds) exp: number; // Expiration timestamp (Unix seconds)}The payload is encrypted using iron-session (AES-256-GCM + HMAC-SHA-256). It cannot be read or tampered with on the client.
createHash
Section titled “createHash”Configures bcrypt password hashing. Automatically applies SHA-256 prehash for passwords exceeding 72 bytes (bcrypt’s input limit).
import { createHash } from 'ideal-auth';
const hash = createHash({ rounds: 14 });
const hashed = await hash.make('my-password');const valid = await hash.verify('my-password', hashed);Config options
Section titled “Config options”| Option | Type | Default | Description |
|---|---|---|---|
rounds | number | 12 | bcrypt cost factor (salt rounds). Higher values are slower but more secure. Each increment roughly doubles the computation time. |
createTokenVerifier
Section titled “createTokenVerifier”Creates signed, expiring tokens for flows like password reset, email verification, and magic links. Tokens are HMAC-signed and contain the user ID and timestamps.
import { createTokenVerifier } from 'ideal-auth';
const verifier = createTokenVerifier({ secret: process.env.IDEAL_AUTH_SECRET!, expiryMs: 1000 * 60 * 15, // 15 minutes});
const token = verifier.createToken(userId);const result = verifier.verifyToken(token);// result: { userId: string, iatMs: number } | nullConfig options
Section titled “Config options”| Option | Type | Default | Description |
|---|---|---|---|
secret | string | — | Required. Signing secret, at least 32 characters. |
expiryMs | number | 3600000 (1 hour) | Token lifetime in milliseconds. |
Return value of verifyToken
Section titled “Return value of verifyToken”Returns null if the token is invalid or expired. Otherwise returns:
| Field | Type | Description |
|---|---|---|
userId | string | The user ID embedded in the token. |
iatMs | number | Issued-at time in milliseconds (not seconds). Useful for single-use checks (e.g., reject if token was issued before the last password change). |
createTOTP
Section titled “createTOTP”Creates a TOTP (Time-based One-Time Password) instance compliant with RFC 6238. Used for two-factor authentication.
import { createTOTP } from 'ideal-auth';
const totp = createTOTP({ digits: 6, period: 30, window: 1,});
const secret = totp.generateSecret();const uri = totp.generateQrUri({ secret, issuer: 'MyApp', account: 'user@example.com',});const valid = totp.verify('123456', secret);Config options
Section titled “Config options”| Option | Type | Default | Description |
|---|---|---|---|
digits | number | 6 | Number of digits in the generated code. |
period | number | 30 | Time step in seconds. A new code is generated every period seconds. |
window | number | 1 | Number of time steps to check before and after the current step. A window of 1 accepts codes from the previous, current, and next period (90-second effective window with a 30-second period). |
Methods
Section titled “Methods”| Method | Returns | Description |
|---|---|---|
generateSecret() | string | Generates a random Base32-encoded secret (20 bytes). |
generateQrUri({ secret, issuer, account }) | string | Returns an otpauth:// URI suitable for QR code generation. |
verify(token, secret) | boolean | Verifies a TOTP code against the secret using timing-safe comparison. |
generateRecoveryCodes
Section titled “generateRecoveryCodes”Generates a set of hashed recovery codes for 2FA backup. Returns both the plaintext codes (to display to the user once) and the hashed versions (to store in your database).
import { createHash, generateRecoveryCodes, verifyRecoveryCode } from 'ideal-auth';
const hash = createHash();
// Generate codesconst { codes, hashed } = await generateRecoveryCodes(hash, 8);// codes: ['a1b2c3d4-e5f6g7h8', ...] — show to user// hashed: ['$2a$12$...', ...] — store in database
// Verify a codeconst result = await verifyRecoveryCode(userInput, storedHashes, hash);// result: { valid: boolean, remaining: string[] }| Parameter | Type | Default | Description |
|---|---|---|---|
hashInstance | HashInstance | — | Required. A createHash() instance for hashing the codes. |
count | number | 8 | Number of recovery codes to generate. |
createRateLimiter
Section titled “createRateLimiter”Creates a rate limiter with a sliding window. Useful for protecting login endpoints from brute-force attacks.
import { createRateLimiter } from 'ideal-auth';
const limiter = createRateLimiter({ maxAttempts: 5, windowMs: 15 * 60 * 1000, // 15 minutes});
const result = await limiter.attempt('login:user@example.com');// result: { allowed: boolean, remaining: number, resetAt: Date }
if (!result.allowed) { throw new Error('Too many attempts. Try again later.');}Config options
Section titled “Config options”| Option | Type | Default | Description |
|---|---|---|---|
maxAttempts | number | — | Required. Maximum number of attempts allowed within the window. |
windowMs | number | — | Required. Time window in milliseconds. The counter resets after this period. |
store | RateLimitStore | MemoryRateLimitStore | Pluggable storage backend. Defaults to an in-memory store. Implement the RateLimitStore interface for Redis or database-backed stores. |
Custom store interface
Section titled “Custom store interface”interface RateLimitStore { increment(key: string, windowMs: number): Promise<{ count: number; resetAt: Date }>; reset(key: string): Promise<void>;}Methods
Section titled “Methods”| Method | Returns | Description |
|---|---|---|
attempt(key) | Promise<RateLimitResult> | Increments the counter for the given key and returns whether the attempt is allowed. |
reset(key) | Promise<void> | Resets the counter for the given key (e.g., after a successful login). |
Crypto utilities
Section titled “Crypto utilities”Low-level cryptographic functions exported for use in custom flows.
import { encrypt, decrypt, signData, verifySignature, timingSafeEqual, generateToken,} from 'ideal-auth';| Function | Signature | Description |
|---|---|---|
encrypt(data, secret) | (string, string) => string | Encrypts a string using the provided secret. |
decrypt(data, secret) | (string, string) => string | Decrypts a string previously encrypted with encrypt. |
signData(data, secret) | (string, string) => string | Creates an HMAC signature for the given data. |
verifySignature(data, signature, secret) | (string, string, string) => boolean | Verifies an HMAC signature. |
timingSafeEqual(a, b) | (string, string) => boolean | Constant-time string comparison to prevent timing attacks. |
generateToken(bytes?) | (number?) => string | Generates a cryptographically random hex token. Default is 32 bytes (64 hex characters). |
Full configuration example
Section titled “Full configuration example”import { createAuth, createHash, createTokenVerifier, createTOTP, createRateLimiter,} from 'ideal-auth';import { cookies } from 'next/headers';import { db } from './db';
// Cookie bridge (Next.js App Router)const cookieBridge = { async get(name: string) { return (await cookies()).get(name)?.value; }, async set(name: string, value: string, options: any) { (await cookies()).set(name, value, options); }, async delete(name: string) { (await cookies()).delete(name); },};
// Password hashingexport const hash = createHash({ rounds: 12 });
// Session authexport const auth = createAuth({ secret: process.env.IDEAL_AUTH_SECRET!, cookie: cookieBridge, session: { cookieName: 'ideal_session', maxAge: 60 * 60 * 24 * 7, // 7 days rememberMaxAge: 60 * 60 * 24 * 30, // 30 days cookie: { secure: true, sameSite: 'lax', path: '/', }, }, resolveUser: (id) => db.user.findUnique({ where: { id } }), resolveUserByCredentials: (creds) => db.user.findUnique({ where: { email: creds.email } }), hash,});
// Token verifier (password reset, email verification)export const tokenVerifier = createTokenVerifier({ secret: process.env.IDEAL_AUTH_SECRET!, expiryMs: 1000 * 60 * 60, // 1 hour});
// TOTP for 2FAexport const totp = createTOTP({ digits: 6, period: 30, window: 1,});
// Rate limiter for loginexport const loginLimiter = createRateLimiter({ maxAttempts: 5, windowMs: 15 * 60 * 1000, // 15 minutes});