Skip to main content

Storage

nauth-toolkit uses two storage layers: a database for permanent authentication data (users, sessions, MFA devices) and a transient storage adapter for temporary state (rate limits, distributed locks, token tracking). Both are required --- the database is provided by a TypeORM DataSource, and the transient adapter is either Redis or a database-backed fallback.

How It Works

LayerPurposeData LifespanLoss ImpactOptions
DatabaseUser accounts, sessions, MFA devices, audit logsPermanentCatastrophicPostgreSQL, MySQL
TransientRate limits, distributed locks, token reuse trackingSeconds to hoursAcceptable --- rebuilds automaticallyRedis (recommended), Database

Database Storage

The database stores all persistent authentication data through TypeORM entities. You include these entities in your DataSource, and nauth-toolkit manages them through its services.

Entities

getNAuthEntities() returns the full set of persistent entities:

EntityPurpose
UserUser accounts and credentials
SessionActive auth sessions
LoginAttemptLogin attempt tracking for lockout
VerificationTokenEmail/phone verification codes
SocialAccountLinked OAuth provider accounts
ChallengeSessionActive challenge flows (MFA, verification)
MFADeviceRegistered MFA devices (TOTP, SMS, Email, Passkey)
AuthAuditAuthentication audit trail
TrustedDeviceRemembered/trusted devices for MFA bypass
SocialProviderSecretEncrypted social provider state (PKCE, nonces)

Supported Databases

npm install @nauth-toolkit/database-typeorm-postgres
import { getNAuthEntities } from '@nauth-toolkit/database-typeorm-postgres';

// Include in your TypeORM DataSource or TypeOrmModule
entities: [...getNAuthEntities(), /* your entities */],

Transient Storage

Transient storage handles short-lived authentication state that must be shared across application instances. The StorageAdapter interface provides key-value, hash, and list operations with TTL support.

What It Stores

DataPurposeTTL
Rate limit countersPrevent brute force (login attempts, SMS/email sends)Configured per limit
Distributed locksPrevent concurrent token refresh race conditionsSeconds
Token reuse markersDetect refresh token replay attacksMatches refresh token TTL
Token familiesTrack token lineage for rotationMatches refresh token TTL

StorageAdapter Interface

Any transient storage backend must implement StorageAdapter. The interface has 21 methods across six categories:

Full method reference

Key-value operations

MethodSignatureDescription
get(key: string) => Promise<string | null>Retrieve a value by key
set(key: string, value: string, ttlSeconds?: number, options?: { nx?: boolean }) => Promise<string | null | void>Store a value with optional TTL and nx (set-if-not-exists)
del(key: string) => Promise<void>Delete a key
exists(key: string) => Promise<boolean>Check if a key exists

Atomic operations

MethodSignatureDescription
incr(key: string, ttlSeconds?: number) => Promise<number>Increment a counter (used by rate limiting)
decr(key: string) => Promise<number>Decrement a counter
expire(key: string, ttl: number) => Promise<void>Set TTL on an existing key
ttl(key: string) => Promise<number>Get remaining TTL in seconds

Hash operations

MethodSignatureDescription
hget(key: string, field: string) => Promise<string | null>Get a single hash field
hset(key: string, field: string, value: string) => Promise<void>Set a single hash field
hgetall(key: string) => Promise<Record<string, string>>Get all fields in a hash
hdel(key: string, ...fields: string[]) => Promise<number>Delete hash fields

List operations (used for token families)

MethodSignatureDescription
lpush(key: string, value: string) => Promise<void>Push to the head of a list
lrange(key: string, start: number, stop: number) => Promise<string[]>Get a range of list elements
llen(key: string) => Promise<number>Get the length of a list

Pattern operations

MethodSignatureDescription
keys(pattern: string) => Promise<string[]>Find keys matching a glob pattern
scan(cursor: number, pattern: string, count: number) => Promise<[number, string[]]>Incrementally iterate keys

Lifecycle

MethodSignatureDescription
initialize() => Promise<void>Called once at startup
isHealthy() => Promise<boolean>Health check for readiness probes
cleanup() => Promise<void>Release resources (graceful shutdown)
disconnect() => Promise<void>Close the underlying connection

Best for production and multi-server deployments. Required when running multiple application instances --- rate limits and locks must be shared.

npm install @nauth-toolkit/storage-redis redis
import { RedisStorageAdapter } from '@nauth-toolkit/storage-redis';
import { createClient } from 'redis';

const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

AuthModule.forRoot({
storageAdapter: new RedisStorageAdapter(redisClient),
});
Redis Cluster

For high availability, RedisStorageAdapter also accepts a cluster client:

import { createCluster } from 'redis';

const cluster = createCluster({
rootNodes: [
{ url: 'redis://node1:6379' },
{ url: 'redis://node2:6379' },
{ url: 'redis://node3:6379' },
],
});
await cluster.connect();

storageAdapter: new RedisStorageAdapter(cluster),

Database Adapter

Simpler setup --- uses your existing database for transient state. Adequate for single-server or low-traffic applications.

npm install @nauth-toolkit/storage-database

When using the database adapter, include transient storage entities in your TypeORM configuration:

import {
getNAuthEntities,
getNAuthTransientStorageEntities,
} from '@nauth-toolkit/database-typeorm-postgres';

entities: [...getNAuthEntities(), ...getNAuthTransientStorageEntities()],

getNAuthTransientStorageEntities() returns two entities: RateLimit and StorageLock.

import { createDatabaseStorageAdapter } from '@nauth-toolkit/nestjs';

AuthModule.forRoot({
storageAdapter: createDatabaseStorageAdapter(),
});
note

createDatabaseStorageAdapter() is a NestJS-specific factory that handles repository injection automatically. For Express/Fastify, use DatabaseStorageAdapter directly.

Database vs Redis trade-offs
RedisDatabase
PerformanceIn-memory, sub-millisecondDisk I/O, slower under load
TTL handlingNative, automatic expirationRequires periodic cleanup
Multi-serverShared state across instancesShared via same database
InfrastructureRequires Redis serverNo additional infrastructure
Best forProduction, multi-instanceDevelopment, single server

Auto-Detection

If you don't provide a storageAdapter in your config, nauth-toolkit automatically creates a DatabaseStorageAdapter when:

  1. Transient storage entities (RateLimit, StorageLock) are included in your TypeORM DataSource
  2. The @nauth-toolkit/storage-database package is installed

This is convenient for getting started, but explicit configuration is recommended for production clarity.

Multi-Server Deployments

Database adapter auto-detection works for single-server setups. If you run multiple application instances, you must use RedisStorageAdapter --- rate limits and distributed locks need to be shared across all instances to work correctly.

Migrating from Database to Redis

Transient storage data is temporary by design --- no data migration is needed. Switch the adapter and the system rebuilds state as new requests come in.

// Before
import { createDatabaseStorageAdapter } from '@nauth-toolkit/nestjs';
storageAdapter: createDatabaseStorageAdapter(),

// After
import { RedisStorageAdapter } from '@nauth-toolkit/storage-redis';
import { createClient } from 'redis';
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();
storageAdapter: new RedisStorageAdapter(redisClient),

After migrating, you can remove getNAuthTransientStorageEntities() from your TypeORM entities array --- Redis handles its own storage.

What's Next

  • Configuration --- Full configuration reference including storage options
  • Rate Limiting --- Configure rate limits that use transient storage
  • Token Management --- How token refresh rotation uses transient storage
  • Quick Start --- End-to-end setup including storage configuration