Skip to content

Configuration

VariableDescriptionRequired
IDEAL_AUTH_SECRETEncryption secret for sessions and token signing. Must be at least 32 characters. Generate one with bunx ideal-auth secret.Yes

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',
});
OptionTypeDefaultDescription
secretstringRequired. Encryption secret, at least 32 characters. Typically process.env.IDEAL_AUTH_SECRET.
cookieCookieBridgeRequired. Object with get, set, and delete methods for your framework’s cookie API. See Getting Started.
sessionobjectSee belowSession 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>undefinedLooks up a user by credentials (with the password field stripped). Used by attempt() in the Laravel-style flow.
hashHashInstanceundefinedA createHash() instance. Required if using resolveUserByCredentials with attempt().
credentialKeystring'password'The key in the credentials object that holds the plaintext password. attempt() strips this key before passing credentials to resolveUserByCredentials.
passwordFieldstring'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>undefinedEscape hatch for full control over credential verification. If provided, attempt() delegates entirely to this function instead of using hash + resolveUserByCredentials.

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.

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;
},
});

The session object on createAuth controls cookie naming, TTL, and cookie attributes.

OptionTypeDefaultDescription
cookieNamestring'ideal_session'Name of the session cookie.
maxAgenumber604800 (7 days)Session lifetime in seconds. Used when remember is not set or remember: false.
rememberMaxAgenumber2592000 (30 days)Extended session lifetime in seconds. Used when login(user, { remember: true }) is called.
cookiePartial<ConfigurableCookieOptions>{}Additional cookie attributes (see table below).

The session.cookie object accepts any cookie attribute except httpOnly, which is always forced to true and cannot be overridden.

OptionTypeDefaultDescription
securebooleantrue in productionWhether 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.
pathstring'/'URL path scope for the cookie.
domainstringundefinedDomain scope for the cookie.

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.


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);
OptionTypeDefaultDescription
roundsnumber12bcrypt cost factor (salt rounds). Higher values are slower but more secure. Each increment roughly doubles the computation time.

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 } | null
OptionTypeDefaultDescription
secretstringRequired. Signing secret, at least 32 characters.
expiryMsnumber3600000 (1 hour)Token lifetime in milliseconds.

Returns null if the token is invalid or expired. Otherwise returns:

FieldTypeDescription
userIdstringThe user ID embedded in the token.
iatMsnumberIssued-at time in milliseconds (not seconds). Useful for single-use checks (e.g., reject if token was issued before the last password change).

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);
OptionTypeDefaultDescription
digitsnumber6Number of digits in the generated code.
periodnumber30Time step in seconds. A new code is generated every period seconds.
windownumber1Number 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).
MethodReturnsDescription
generateSecret()stringGenerates a random Base32-encoded secret (20 bytes).
generateQrUri({ secret, issuer, account })stringReturns an otpauth:// URI suitable for QR code generation.
verify(token, secret)booleanVerifies a TOTP code against the secret using timing-safe comparison.

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 codes
const { codes, hashed } = await generateRecoveryCodes(hash, 8);
// codes: ['a1b2c3d4-e5f6g7h8', ...] — show to user
// hashed: ['$2a$12$...', ...] — store in database
// Verify a code
const result = await verifyRecoveryCode(userInput, storedHashes, hash);
// result: { valid: boolean, remaining: string[] }
ParameterTypeDefaultDescription
hashInstanceHashInstanceRequired. A createHash() instance for hashing the codes.
countnumber8Number of recovery codes to generate.

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.');
}
OptionTypeDefaultDescription
maxAttemptsnumberRequired. Maximum number of attempts allowed within the window.
windowMsnumberRequired. Time window in milliseconds. The counter resets after this period.
storeRateLimitStoreMemoryRateLimitStorePluggable storage backend. Defaults to an in-memory store. Implement the RateLimitStore interface for Redis or database-backed stores.
interface RateLimitStore {
increment(key: string, windowMs: number): Promise<{ count: number; resetAt: Date }>;
reset(key: string): Promise<void>;
}
MethodReturnsDescription
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).

Low-level cryptographic functions exported for use in custom flows.

import {
encrypt,
decrypt,
signData,
verifySignature,
timingSafeEqual,
generateToken,
} from 'ideal-auth';
FunctionSignatureDescription
encrypt(data, secret)(string, string) => stringEncrypts a string using the provided secret.
decrypt(data, secret)(string, string) => stringDecrypts a string previously encrypted with encrypt.
signData(data, secret)(string, string) => stringCreates an HMAC signature for the given data.
verifySignature(data, signature, secret)(string, string, string) => booleanVerifies an HMAC signature.
timingSafeEqual(a, b)(string, string) => booleanConstant-time string comparison to prevent timing attacks.
generateToken(bytes?)(number?) => stringGenerates a cryptographically random hex token. Default is 32 bytes (64 hex characters).

lib/auth.ts
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 hashing
export const hash = createHash({ rounds: 12 });
// Session auth
export 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 2FA
export const totp = createTOTP({
digits: 6,
period: 30,
window: 1,
});
// Rate limiter for login
export const loginLimiter = createRateLimiter({
maxAttempts: 5,
windowMs: 15 * 60 * 1000, // 15 minutes
});