Skip to main content

Configuration

This page provides a complete reference for configuring nauth-toolkit, including all available options and best practices.

Configuration File Approach

Best Practice: Store your configuration in a separate file, not in your AppModule or main.ts.

This keeps your configuration organized, testable, and easy to maintain across environments.

Configuration Sources

Load sensitive values (secrets, credentials) from your preferred configuration source:

  • Environment variables (process.env)
  • NestJS ConfigService
  • AWS Secrets Manager / Azure Key Vault
  • Configuration files (.env, config.json)

The toolkit is agnostic to how you manage configuration - use what works best for your infrastructure.

1. Create configuration file:

src/config/auth.config.ts
import { NAuthModuleConfig, createRedisStorageAdapter } from '@nauth-toolkit/nestjs';
import { NodemailerEmailProvider } from '@nauth-toolkit/email-nodemailer';
import { Logger } from '@nestjs/common';

export const authConfig: NAuthModuleConfig = {
jwt: {
algorithm: 'HS256',
accessToken: {
secret: process.env.JWT_SECRET,
expiresIn: '15m',
},
refreshToken: {
secret: process.env.JWT_REFRESH_SECRET,
expiresIn: '7d',
reuseDetection: true,
},
},

storageAdapter: createRedisStorageAdapter(process.env.REDIS_URL || 'redis://localhost:6379'),

emailProvider: new NodemailerEmailProvider({
transport: {
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
},
defaults: {
from: `My App <${process.env.EMAIL_FROM || 'noreply@example.com'}>`,
},
}),

email: {
globalVariables: {
appName: 'My App',
supportEmail: process.env.SUPPORT_EMAIL,
},
},

logger: {
instance: new Logger('NAuth'),
enablePiiRedaction: true,
},
};

2. Import in AppModule:

src/app.module.ts
import { Module } from '@nestjs/common';
import { AuthModule } from '@nauth-toolkit/nestjs';
import { authConfig } from './config/auth.config';

@Module({
imports: [AuthModule.forRoot(authConfig)],
})
export class AppModule {}

Core Configuration

Top-level options (NAuthConfig)

These are the top-level keys you can provide in NAuthConfig / NAuthModuleConfig. Each links to its detailed section below.

OptionRequiredDescription
tablePrefixNoDatabase table prefix. Default: nauth_. Example: 'myapp_' → tables become myapp_users, myapp_sessions.
jwtYesJWT signing algorithm, secrets, and token lifetimes.
storageAdapterNo*Transient storage for sessions, rate limits, and token reuse detection. Auto-detected if omitted and DB storage entities are registered.
signupNoSignup toggle, verification method (email/phone/both/none), and rate limits.
loginNoLogin identifier policy: email, username, phone, or email_or_username.
passwordNoPassword policy (length, complexity, history, expiry) and password reset flow.
lockoutNoIP-based failed-login lockout.
securityNoCSRF protection and sensitive data masking in challenge responses.
tokenDeliveryNoHow tokens reach clients: json (response body), cookies (httpOnly), or hybrid. Default: json.
sessionNoSession concurrency limits and hard maximum lifetime.
emailProviderNo*Email provider instance. Required when email verification or MFA email is enabled.
emailNoEmail branding (globalVariables) and custom HTML/text templates.
emailNotificationsNoPer-event suppression controls. Optional lifecycle emails are disabled by default.
smsProviderNo*SMS provider instance. Required when phone verification or MFA SMS is enabled.
smsNoSMS template branding and custom message content.
socialNoSocial OAuth providers (Google, Apple, Facebook) and post-OAuth redirect config.
mfaNoMFA methods, enforcement mode (OPTIONAL/REQUIRED/ADAPTIVE), and device trust.
geoLocationNoMaxMind IP geolocation database. Required for adaptive MFA.
auditLogsNoAudit trail for authentication and security events. Default: enabled.
recaptchaNoGoogle reCAPTCHA bot protection (v2, v3, Enterprise).
challengeNoMax verification attempts before a challenge session is invalidated. Default: 3.
loggerNoLogger instance and PII redaction controls. Silent by default.

JWT Configuration

Controls JWT token generation and validation. JWTs are used for stateless authentication - the access token authorizes API requests, while the refresh token allows obtaining new access tokens without re-authentication.

jwt: {
algorithm: 'HS256', // HS256/HS384/HS512 (symmetric) or RS256/RS384/RS512 (asymmetric)
issuer: 'com.myapp',
audience: ['web', 'mobile'],

accessToken: {
secret: process.env.JWT_SECRET, // Required for HS* algorithms
// privateKey: process.env.PRIVATE_KEY, // Required for RS* algorithms
// publicKey: process.env.PUBLIC_KEY, // Required for RS* algorithms
expiresIn: '15m', // 15 minutes
},

refreshToken: {
secret: process.env.JWT_REFRESH_SECRET,
expiresIn: '7d', // 7 days
reuseDetection: true, // Detect and block token reuse attacks
},
}

Configuration Options:

OptionDescriptionDefaultRecommended
algorithmSigning algorithm. HS256 uses symmetric key (same secret for sign/verify). RS256 uses asymmetric keys (private key signs, public key verifies) - better for microservices where multiple services verify tokens.HS256HS256 for monoliths, RS256 for microservices
issuerIdentifies who issued the token. Used for validation - tokens from other issuers are rejected.noneYour app identifier (e.g., com.myapp)
audienceWho the token is intended for. Can be a string or array. Tokens are rejected if audience doesn't match.noneArray of client types (e.g., web, mobile)
accessToken.secretREQUIRED (HS* algorithms). Secret key for HS* algorithms. Must be strong and kept secure. Minimum 32 characters (256 bits).N/A256-bit random string from secure config
accessToken.privateKeyREQUIRED (RS* algorithms). PEM-encoded private key for RS* algorithms. Used to sign tokens.N/ARSA private key from secure config
accessToken.publicKeyREQUIRED (RS* algorithms). PEM-encoded public key for RS* algorithms. Used to verify tokens. Can be shared publicly.N/ARSA public key from secure config
accessToken.expiresInREQUIRED. How long access tokens are valid. Short-lived for security (if stolen, limited damage). Format: 15m, 1h, or seconds.N/A15m (15 minutes)
refreshToken.secretREQUIRED. Secret for signing refresh tokens. Should be different from access token secret. Minimum 32 characters (256 bits).N/A256-bit random string from secure config
refreshToken.expiresInREQUIRED. How long refresh tokens are valid. Longer-lived for better UX (users stay logged in). Format: 7d, 30d, or seconds.N/A7d to 30d
refreshToken.rotationDeprecated. Token rotation is always enabled and cannot be disabled. A new refresh token is issued on every use regardless of this flag. Accepted for backward compatibility but ignored at runtime.N/AOmit (always on)
refreshToken.reuseDetectionDetect when an old refresh token is reused (sign of theft). When detected, invalidates entire token family and forces re-authentication.falsetrue

When to use RS256 vs HS256:

  • HS256 (Symmetric): Single application, simpler setup, faster
  • RS256 (Asymmetric): Microservices, multiple services verify tokens, public key can be shared

Key Points:

  • algorithm: Use HS256 for simplicity, RS256 for microservices
  • accessToken.expiresIn: Short-lived (15m recommended)
  • refreshToken.expiresIn: Long-lived (7d-30d)
  • reuseDetection: Recommended for production

Storage Adapter

Storage adapter is REQUIRED. Choose between Redis or Database for transient storage.

// Redis (Recommended for production, multi-server)
import { createRedisStorageAdapter } from '@nauth-toolkit/nestjs';
storageAdapter: createRedisStorageAdapter('redis://localhost:6379'),

// Database (No Redis needed) - NestJS
import { createDatabaseStorageAdapter } from '@nauth-toolkit/nestjs';
storageAdapter: createDatabaseStorageAdapter(),

// Database (No Redis needed) - Express/Fastify
// import { DatabaseStorageAdapter } from '@nauth-toolkit/storage-database';
// storageAdapter: new DatabaseStorageAdapter(),
tip

If you don't provide a storageAdapter explicitly, DatabaseStorageAdapter will be auto-created if storage entities (getNAuthTransientStorageEntities()) are included in your TypeORM configuration.

See Storage for detailed comparison and auto-detection behavior.

Login Configuration

Controls which identifier types are accepted during login.

identifierTypeAccepts
undefined (default)All types: email, username, phone
'email'Email addresses only
'username'Usernames only
'phone'Phone numbers only
'email_or_username'Email or username (phone excluded)
login: {
identifierType: 'email_or_username',
},

Email Provider

Configure email delivery for verification codes and notifications.

import { NodemailerEmailProvider } from '@nauth-toolkit/email-nodemailer';

emailProvider: new NodemailerEmailProvider({
transport: {
host: process.env.SMTP_HOST,
port: 587,
secure: false, // false = STARTTLS on port 587; true = direct TLS on port 465
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
},
defaults: {
from: 'My App <noreply@myapp.com>',
},
}),

email: {
globalVariables: {
appName: 'My App',
companyName: 'My Company Inc.',
supportEmail: 'support@myapp.com',
logoUrl: 'https://myapp.com/logo.png',
brandColor: '#4f46e5',
},
},

Email Notifications (Optional)

Optional notification emails are opt-in and controlled via emailNotifications.suppress. This is separate from template customization (see Email Templates).

emailNotifications: {
enabled: true,
suppress: {
// Optional notifications — default: true (suppressed/disabled). Set false to enable.
welcome: false,
passwordChanged: false,
mfaDeviceRemoved: false,
mfaFirstEnabled: false, // Uses the mfaEnabled email template
mfaMethodAdded: false,
adaptiveMfaRiskDetected: false, // Uses the adaptiveMfaRiskAlert template
sessionsRevoked: false,
accountLockout: false,
accountDisabled: false,
accountEnabled: false,
emailChangedOld: false, // Alert sent to the old email address
emailChangedNew: false, // Confirmation sent to the new email address
// Note: Code emails (emailVerification, passwordReset, adminPasswordReset)
// cannot be suppressed and are always sent when enabled: true
},
},
Naming tip

Some notification keys map to a differently-named template type:

  • mfaFirstEnabled notification uses the mfaEnabled email template (TemplateType.MFA_ENABLED)
  • adaptiveMfaRiskDetected notification uses the adaptiveMfaRiskAlert email template (TemplateType.ADAPTIVE_MFA_RISK_ALERT)

SMS Provider

Configure SMS delivery for phone verification, MFA, and password reset.

import { AWSSMSProvider } from '@nauth-toolkit/sms-aws-sns';

smsProvider: new AWSSMSProvider({
region: process.env.AWS_REGION as string,
originationNumber: process.env.AWS_SMS_ORIGINATION as string, // e.g. '+12345678901' or 'MyApp'
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
// apiMode: 'end-user-messaging-sms', // optional; use for configuration sets
// configurationSetName: 'default',
}),

SMS Templates

Customize SMS message content globally (branding + custom templates).

import { ConsoleSMSProvider } from '@nauth-toolkit/sms-console';

smsProvider: new ConsoleSMSProvider(),

sms: {
templates: {
globalVariables: {
appName: 'My App',
supportPhone: '+1-800-123-4567',
},
customTemplates: {
verification: {
content:
'{{#if appName}}{{appName}}: {{/if}}Your verification code is {{code}}. Valid for {{expiryMinutes}} minutes. Need help? {{supportPhone}}',
},
mfa: {
content: '{{#if appName}}{{appName}}: {{/if}}Your MFA code is {{code}}. Valid for {{expiryMinutes}} minutes.',
},
passwordReset: {
content:
'{{#if appName}}{{appName}}: {{/if}}Your password reset code is {{code}}. Valid for {{expiryMinutes}} minutes.',
},
},
},
},

See SMS Templates Feature Guide and SMS Templates Configuration.

Logger

Configure logging with PII redaction.

import { Logger } from '@nestjs/common';

logger: {
instance: new Logger('NAuth'),
enablePiiRedaction: true, // Redact emails, IPs, tokens
logLevel: 'debug',
},

Signup Configuration

Controls user registration behavior, verification requirements, and anti-abuse measures.

signup: {
enabled: true,
verificationMethod: 'email', // 'none' | 'email' | 'phone' | 'both'
allowDuplicatePhones: false,

emailVerification: {
expiresIn: 3600, // 1 hour
maxAttempts: 3, // Max attempts per code
resendDelay: 60, // 60 seconds
rateLimitMax: 3, // 3 emails per window
rateLimitWindow: 3600, // 1 hour
maxAttemptsPerUser: 10,
maxAttemptsPerIP: 20,
attemptWindow: 3600,
baseUrl: 'https://myapp.com', // Optional: include verification link in emails
},

phoneVerification: {
codeLength: 6,
expiresIn: 300, // 5 minutes
maxAttempts: 3,
resendDelay: 60,
rateLimitMax: 3,
rateLimitWindow: 3600,
maxAttemptsPerUser: 10,
maxAttemptsPerIP: 20,
attemptWindow: 3600,
},
},

Verification Methods:

MethodDescriptionUse Case
'none'No verification required. Users can login immediately after signup.Internal tools, trusted environments, development
'email'Email verification required before login. User receives verification code/link.Standard web apps, most common choice
'phone'Phone verification required before login. User receives SMS code.Mobile-first apps, regions where phone is primary contact
'both'Both email AND phone verification required. Higher security but more friction.High-security apps, financial services

Email Verification Options:

OptionDescriptionDefaultRecommended
expiresInHow long verification codes/links are valid (seconds). Balance security vs UX.36003600 (1 hour)
maxAttemptsMax attempts to verify a single code. Prevents brute force.33
resendDelayMinimum time between resend requests (seconds). Prevents spam.6060 (1 minute)
rateLimitMaxMaximum verification emails per time window. Prevents email bombing.33
rateLimitWindowTime window for rate limiting (seconds).36003600 (1 hour)
maxAttemptsPerUserMax verification attempts per user per window. Prevents brute force.1010
maxAttemptsPerIPMax verification attempts per IP per window. Prevents distributed attacks.2020
attemptWindowTime window for attempt limits (seconds).36003600 (1 hour)
baseUrlBase URL for verification links. If provided, emails include clickable link with code. Format: ${baseUrl}?code=${code}. The consumer app handles routing. Supports localhost (e.g., http://localhost:4200).nonehttps://myapp.com or http://localhost:4200 for development

Phone Verification Options:

OptionDescriptionDefaultRecommended
codeLengthLength of SMS verification code. Longer = more secure but harder to type.66
expiresInHow long SMS codes are valid (seconds). Shorter for security (SMS can be intercepted).300300 (5 minutes)
maxAttemptsMax attempts to verify a single code. Prevents brute force.33
resendDelayMinimum time between SMS resends (seconds). Prevents SMS spam and cost abuse.6060 (1 minute)
rateLimitMaxMaximum SMS per time window. Important: SMS costs money, prevent abuse!33
rateLimitWindowTime window for SMS rate limiting (seconds).36003600 (1 hour)
maxAttemptsPerUserMax verification attempts per user per window.1010
maxAttemptsPerIPMax verification attempts per IP per window.2020
attemptWindowTime window for attempt limits (seconds).36003600 (1 hour)

Other Options:

OptionDescriptionUse Case
enabledEnable/disable user signups globally.Set to false to close registration (invite-only apps)
allowDuplicatePhonesAllow multiple users with same phone number.Family accounts, shared devices

Security Considerations:

  • Rate Limiting: Prevents attackers from sending thousands of verification emails/SMS
  • Attempt Limits: Prevents brute-forcing verification codes
  • IP-based Limits: Stops distributed attacks from multiple accounts
  • Expiration: Short expiration windows reduce attack surface

Password & Security

Configure password policies, account lockout, and CSRF protection.

password: {
minLength: 8,
maxLength: 128,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
preventCommon: true, // Block common passwords
preventUserInfo: true, // Block passwords containing email/username
historyCount: 5, // Prevent reusing last 5 passwords
expiryDays: 90, // Force password change after 90 days (0 = disabled)
},

lockout: {
enabled: true,
maxAttempts: 5, // Lock after 5 failed attempts
duration: 900, // 15 minutes lockout
resetOnSuccess: true, // Reset counter on successful login
},

security: {
maskSensitiveData: true, // Mask email/phone in challenge responses
csrf: {
cookieName: 'csrf-token',
headerName: 'x-csrf-token',
tokenLength: 32,
excludedPaths: ['/webhook'], // Paths that don't need CSRF
cookieOptions: {
secure: true,
sameSite: 'strict',
domain: '.myapp.com', // Optional: share across subdomains
},
},
},

maskSensitiveData:

When enabled (default), email addresses and phone numbers in challenge responses are masked for security:

  • Email: john.doe@example.comj***@example.com
  • Phone: +1234567890***-***-7890

Set to false to return full email/phone in challenge responses. Useful for development or internal apps where security is less critical.

security: {
maskSensitiveData: false, // Return full email/phone
},

Password Policy Options:

OptionDescriptionDefaultRecommended
minLengthMinimum password length. NIST recommends at least 8 characters.88 or higher
maxLengthMaximum password length. Prevents DoS attacks from bcrypt hashing very long strings.128128
requireUppercaseRequire at least one uppercase letter (A-Z).falsetrue for moderate security
requireLowercaseRequire at least one lowercase letter (a-z).falsetrue for moderate security
requireNumbersRequire at least one number (0-9).falsetrue for moderate security
requireSpecialCharsRequire at least one special character. Defaults to !@#$%^&* if specialChars is not set.falsetrue for high security
specialCharsCustom set of characters that satisfy requireSpecialChars. Overrides the default !@#$%^&* set.none'$#!@' or domain-appropriate set
preventCommonBlock common passwords (e.g., password123, qwerty). Uses built-in list of 10,000+ common passwords.falsetrue (strongly recommended)
preventUserInfoBlock passwords containing user's email or username. Prevents john@example.com using john123.falsetrue
historyCountNumber of previous passwords to remember. Prevents password reuse. 0 = disabled.05 for compliance, 0 for simplicity
expiryDaysForce password change after N days. 0 = never expires. Note: NIST no longer recommends forced expiration.00 (disabled) or 90 for compliance

Password Reset Options

Controls the forgot-password flow (code delivery is handled by your configured email/SMS provider).

OptionDescriptionDefault
codeLengthVerification code length sent to the user.6
expiresInCode expiry in seconds.900 (15 minutes)
rateLimitMaxMaximum reset requests per time window. Prevents abuse.3
rateLimitWindowTime window for rate limiting (seconds).3600 (1 hour)
maxAttemptsMaximum code verification attempts per code. Prevents brute force.3
password: {
// ... policy options ...
passwordReset: {
codeLength: 6,
expiresIn: 900, // 15 minutes
rateLimitMax: 3,
rateLimitWindow: 3600,
maxAttempts: 3,
},
},

Admin Password Reset Options

Controls admin-initiated password resets. Longer expiry by default; no rate limiting (admin-initiated).

OptionDescriptionDefault
codeLengthVerification code length.6
expiresInCode expiry in seconds. Longer than user-initiated (admin context).3600 (1 hour)
maxAttemptsMaximum code verification attempts per code.3

Account Lockout Options:

OptionDescriptionDefaultRecommended
enabledEnable IP-based account lockout after failed login attempts.falsetrue
maxAttemptsNumber of failed login attempts before lockout. Too low = UX issues, too high = security risk.55
attemptWindowTime window in seconds over which maxAttempts is counted. Prevents indefinite accumulation.36003600 (1 hour)
durationLockout duration in seconds. After this time, the lockout lifts.900900 (15 minutes)
resetOnSuccessReset failed attempt counter on successful login. Recommended for better UX.truetrue

Why IP-based lockout? Uses IP addresses instead of user identifiers. This prevents attackers from locking out legitimate users by repeatedly trying their email/username.

CSRF Protection Options:

OptionDescriptionDefaultRecommended
cookieNameName of the CSRF token cookie.nauth_csrf_tokennauth_csrf_token
headerNameHTTP header name where frontend sends CSRF token.x-csrf-tokenx-csrf-token
tokenLengthLength of CSRF token in bytes. Longer = more secure.3232 (256 bits)
excludedPathsPaths that don't require CSRF tokens (e.g., webhooks, public APIs).empty array/webhook, /api/public as needed
cookieOptions.secureRequire HTTPS for CSRF cookie. Set to false for localhost development.truetrue (false for localhost)
cookieOptions.sameSiteSameSite cookie attribute. strict = most secure, lax = allows top-level navigation.strictstrict
cookieOptions.domainCookie domain for subdomain sharing.none.myapp.com for subdomains

When is CSRF protection needed?

  • Required when using tokenDelivery.method = 'cookies' or 'hybrid'
  • Not needed when using tokenDelivery.method = 'json' (Bearer tokens are CSRF-safe)

Token Delivery

Control how tokens are delivered to clients.

tokenDelivery: {
method: 'cookies', // 'json' | 'cookies' | 'hybrid'
cookieNamePrefix: 'nauth_',

cookieOptions: {
secure: true, // HTTPS only (set to false for localhost)
sameSite: 'strict', // 'strict' | 'lax' | 'none'
domain: '.myapp.com', // Optional: for subdomain sharing
path: '/',
},
},

Delivery Methods:

MethodDescriptionBest ForCSRF Required?
'json'Tokens returned in response body only. Frontend stores in memory/localStorage. Sent via Authorization: Bearer header.Mobile apps, SPAs, API-first appsNo
'cookies'Tokens set as httpOnly cookies. Browser automatically sends with requests. More secure (XSS-resistant).Traditional web apps, server-rendered appsYes
'hybrid'Cookies for web origins, JSON for mobile origins. Best of both worlds.Apps with both web and mobile clientsYes (for web)

Cookie Options:

OptionDescriptionDefaultRecommended
cookieNamePrefixPrefix for all auth cookies. Prevents conflicts with other cookies.nauth_nauth_
secureRequire HTTPS. Cookies won't be sent over HTTP. Set to false for localhost development.truetrue (false for localhost)
sameSiteControls when cookies are sent. strict = same-site only, lax = allows top-level navigation, none = all requests (requires secure: true).strictstrict (or lax for better UX)
domainCookie domain. Set to .myapp.com to share cookies across app.myapp.com and api.myapp.com.none.myapp.com for subdomain sharing
pathCookie path. Usually / for all routes.//

Security Comparison:

AspectJSON (localStorage)Cookies (httpOnly)
XSS Protection Vulnerable - XSS can steal tokens Protected - httpOnly prevents JS access
CSRF Protection Not vulnerable Requires CSRF tokens
Mobile Support Easy Requires cookie handling
Subdomain Sharing Manual implementation Built-in via domain option
Auto-send Must add header manually Browser sends automatically

Recommendation:

  • Web apps: Use 'cookies' for better security
  • Mobile apps: Use 'json' for simplicity
  • Both: Use 'hybrid' mode

Hybrid policy (tokenDelivery.method = 'hybrid')

In hybrid mode, nauth-toolkit chooses between cookies vs JSON delivery based on request origin.

tokenDelivery: {
method: 'hybrid',
hybridPolicy: {
webOrigins: ['https://app.myapp.com'],
nativeOrigins: ['capacitor://localhost', 'ionic://localhost'],
},
},

Session Configuration

Manage user sessions and concurrency.

session: {
maxConcurrent: 5, // Max active sessions per user
disallowMultipleSessions: false, // If true, only 1 session allowed
maxLifetime: '30d', // Hard limit: force re-auth after 30 days
},

Social Login

Configure OAuth providers.

social: {
// social.redirect is required when any provider is enabled — see Social Redirect below
redirect: {
frontendBaseUrl: process.env.FRONTEND_BASE_URL || 'https://app.myapp.com',
},

google: {
enabled: !!process.env.GOOGLE_CLIENT_ID,
clientId: process.env.GOOGLE_CLIENT_ID,
// Or array for multi-platform: [webClientId, iosClientId, androidClientId]
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackUrl: `${process.env.API_BASE_URL}/auth/social/google/callback`,
scopes: ['openid', 'email', 'profile'],
autoLink: true, // Auto-link to existing account by email match
allowSignup: true, // Create new account if no match found
},

apple: {
enabled: !!process.env.APPLE_CLIENT_ID,
clientId: process.env.APPLE_CLIENT_ID, // Apple Services ID (e.g., com.myapp.services)
// Apple requires a JWT client secret for web OAuth. The toolkit generates and refreshes
// it automatically from your Apple Developer credentials (stored in DB, refreshed ~30 days before expiry).
teamId: process.env.APPLE_TEAM_ID, // Apple Developer Team ID
keyId: process.env.APPLE_KEY_ID, // Key ID (kid) from your .p8 key file
privateKeyPem: process.env.APPLE_PRIVATE_KEY_PEM, // Contents of your .p8 file as PEM string
callbackUrl: `${process.env.API_BASE_URL}/auth/social/apple/callback`,
scopes: ['name', 'email'],
autoLink: true,
allowSignup: true,
},

facebook: {
enabled: !!process.env.FACEBOOK_CLIENT_ID,
clientId: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
callbackUrl: `${process.env.API_BASE_URL}/auth/social/facebook/callback`,
scopes: ['email', 'public_profile'],
autoLink: true,
allowSignup: true,
},
},

Social Redirect (social.redirect)

Required when social login is enabled

social.redirect.frontendBaseUrl is required whenever any social provider is enabled. The Zod schema will throw a validation error at startup if it is missing.

After OAuth completes, the backend redirects the user back to your frontend. Configure where that redirect lands:

social: {
redirect: {
frontendBaseUrl: process.env.FRONTEND_BASE_URL || 'https://app.myapp.com',
// Allow returnTo query param to specify a post-login destination URL.
// false = relative paths only (safest); true = absolute URLs allowed (requires allowedReturnToOrigins)
allowAbsoluteReturnTo: false,
// Required when allowAbsoluteReturnTo is true — prevents open-redirect attacks
allowedReturnToOrigins: ['https://app.myapp.com', 'https://admin.myapp.com'],
},
google: { enabled: true, ... },
},
OptionDescriptionDefault
frontendBaseUrlRequired. Base URL of your frontend app. Used to resolve relative returnTo redirect paths after OAuth.
allowAbsoluteReturnToAllow absolute URLs in returnTo query param. Enable only for multi-frontend setups.false
allowedReturnToOriginsOrigin allowlist enforced when allowAbsoluteReturnTo is true. Prevents open redirects.

Multi-Factor Authentication (MFA)

Configure MFA methods and enforcement.

import { MFAMethod } from '@nauth-toolkit/core';

mfa: {
enabled: true,
enforcement: 'OPTIONAL', // 'OPTIONAL' | 'REQUIRED' | 'ADAPTIVE'
gracePeriod: 7, // Days before REQUIRED enforcement kicks in
requireForSocialLogin: false,

allowedMethods: [
MFAMethod.TOTP, // Authenticator apps
MFAMethod.SMS, // SMS codes
MFAMethod.EMAIL, // Email codes
MFAMethod.PASSKEY, // WebAuthn/biometric
],

issuer: 'My App',

totp: {
window: 1, // Time steps to check (±1)
stepSeconds: 30, // Standard TOTP interval
digits: 6,
algorithm: 'sha1',
},

passkey: {
rpName: 'My App',
rpId: 'myapp.com',
origin: ['https://myapp.com', 'https://app.myapp.com'],
timeout: 60000,
userVerification: 'preferred',
},

adaptive: {
triggers: ['new_device', 'new_ip', 'new_country', 'impossible_travel', 'suspicious_activity', 'recent_password_reset'],
riskLevels: {
low: { maxScore: 20, action: 'allow', notifyUser: false },
medium: { maxScore: 50, action: 'require_mfa', notifyUser: true },
high: { maxScore: 100, action: 'require_mfa', notifyUser: true },
},
blockedSignIn: {
// Block scope controls the blast radius of `block_signin`.
// - 'ip': block the suspicious IP
// - 'device': block the device token
// - 'user': block the whole user (strongest, highest DoS risk)
scope: 'ip',
blockDuration: 15, // minutes
message: 'Sign-in blocked due to suspicious activity. Please try again shortly or contact support.',
},
},

rememberDevices: 'user_opt_in', // 'never' | 'always' | 'user_opt_in'
rememberDeviceDays: 30,
bypassMFAForTrustedDevices: true,

backup: {
enabled: true,
codeCount: 10,
codeLength: 8,
},
},

Geolocation

Configure IP geolocation for adaptive MFA.

geoLocation: {
maxMind: {
licenseKey: process.env.MAXMIND_LICENSE_KEY,
accountId: parseInt(process.env.MAXMIND_ACCOUNT_ID || '0'),
dbPath: '/app/data/maxmind', // Optional: defaults to system temp directory
autoDownloadOnStartup: true, // true = download DB files on startup (requires licenseKey + accountId)
editions: ['GeoLite2-City', 'GeoLite2-Country'],
skipDownloads: false, // true = manage DB files externally (CI/CD pre-download pattern)
},
},

Audit Logs

Configure audit trail.

auditLogs: {
enabled: true,
fireAndForget: false, // Set true for performance (no await)
},

reCAPTCHA

Protect authentication endpoints from bot attacks using Google reCAPTCHA v2, v3, or Enterprise.

note

Social OAuth endpoints (/auth/social/*) are not protected by reCAPTCHA — OAuth providers handle their own bot protection.

npm install @nauth-toolkit/recaptcha
import { RecaptchaV3Provider } from '@nauth-toolkit/recaptcha';

recaptcha: {
enabled: true,
provider: new RecaptchaV3Provider({
secretKey: process.env.RECAPTCHA_SECRET_KEY,
}),
minimumScore: 0.5, // v3/Enterprise only. 0.0 = bot, 1.0 = human
actionScores: { // Optional: per-action overrides
login: 0.3, // More permissive for returning users
signup: 0.7, // Stricter for new registrations
},
validateOnStartup: 'warn', // 'warn' | 'error' | false
},

reCAPTCHA Options:

OptionDescriptionDefault
enabledEnable reCAPTCHA validation on auth endpoints.false
providerProvider instance. Choose RecaptchaV2Provider, RecaptchaV3Provider, or RecaptchaEnterpriseProvider.
minimumScoreDefault minimum score (0.0–1.0). Used when no per-action override exists in actionScores. v3/Enterprise only.0.5
actionScoresPer-action score overrides (Record<string, number>). Keys: login, signup, password_reset, etc. Falls back to minimumScore.
validateOnStartupValidate credentials at startup. 'warn': log warning on failure. 'error': halt startup. false: skip.'warn'

Choosing a provider:

ProviderInteractionBest For
RecaptchaV3ProviderInvisible, score-basedMost web apps — no user friction
RecaptchaV2ProviderCheckbox ("I'm not a robot")Highest-risk actions, legacy setups
RecaptchaEnterpriseProviderScore-based with advanced signalsEnterprise — requires GCP project

Challenge Configuration

Challenge session limits for flows like verification, MFA, and step-up challenges.

challenge: {
maxAttempts: 3, // Default: 3
},

Complete Example

Here's a production-ready configuration:

src/config/auth.config.ts
import { NAuthModuleConfig, MFAMethod, createRedisStorageAdapter } from '@nauth-toolkit/nestjs';
import { NodemailerEmailProvider } from '@nauth-toolkit/email-nodemailer';
import { AWSSMSProvider } from '@nauth-toolkit/sms-aws-sns';
import { Logger } from '@nestjs/common';

export const authConfig: NAuthModuleConfig = {
tablePrefix: 'nauth_',

storageAdapter: createRedisStorageAdapter(process.env.REDIS_URL || 'redis://localhost:6379'),

jwt: {
algorithm: 'HS256',
issuer: 'com.myapp',
audience: ['web', 'mobile'],
accessToken: { secret: process.env.JWT_SECRET, expiresIn: '15m' },
refreshToken: {
secret: process.env.JWT_REFRESH_SECRET,
expiresIn: '7d',
reuseDetection: true,
},
},

logger: {
instance: new Logger('NAuth'),
enablePiiRedaction: true,
logLevel: 'log', // 'error' | 'warn' | 'log' | 'debug' | 'verbose'
},

signup: {
enabled: true,
verificationMethod: 'email',
emailVerification: {
expiresIn: 3600,
resendDelay: 60,
rateLimitMax: 3,
rateLimitWindow: 3600,
baseUrl: 'https://myapp.com', // Optional: include verification link in emails
},
},

password: {
minLength: 8,
requireUppercase: true,
requireNumbers: true,
requireSpecialChars: true,
preventCommon: true,
},

lockout: {
enabled: true,
maxAttempts: 5,
duration: 900,
},

tokenDelivery: {
method: 'cookies',
cookieOptions: {
secure: true,
sameSite: 'strict',
},
},

security: {
csrf: {
cookieName: 'csrf-token',
headerName: 'x-csrf-token',
},
},

// Option 1: SMTP Transport
emailProvider: new NodemailerEmailProvider({
transport: {
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
},
defaults: {
from: `My App <${process.env.EMAIL_FROM || 'noreply@myapp.com'}>`,
},
}),

// Option 2: AWS SES SDK Transport (with IAM roles)
// import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
// emailProvider: new NodemailerEmailProvider({
// transport: {
// SES: {
// sesClient: new SESv2Client({ region: process.env.AWS_REGION || 'us-east-1' }),
// SendEmailCommand,
// },
// },
// defaults: {
// from: 'My App <noreply@myapp.com>',
// },
// }),

email: {
globalVariables: {
appName: 'My App',
companyName: 'My Company Inc.',
supportEmail: 'support@myapp.com',
logoUrl: 'https://myapp.com/logo.png',
brandColor: '#4f46e5',
},
},

smsProvider: new AWSSMSProvider({
region: process.env.AWS_REGION as string,
originationNumber: process.env.AWS_SMS_ORIGINATION as string,
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
// apiMode: 'end-user-messaging-sms',
// configurationSetName: 'default',
}),

sms: {
templates: {
globalVariables: {
appName: 'My App',
},
},
},

social: {
redirect: {
frontendBaseUrl: process.env.FRONTEND_BASE_URL || 'https://app.myapp.com',
},
google: {
enabled: true,
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackUrl: `${process.env.API_BASE_URL || 'https://api.myapp.com'}/auth/social/google/callback`,
autoLink: true,
allowSignup: true,
},
},

mfa: {
enabled: true,
enforcement: 'OPTIONAL',
allowedMethods: [MFAMethod.TOTP, MFAMethod.EMAIL],
issuer: 'My App',
},

session: {
maxConcurrent: 5,
maxLifetime: '30d',
},

auditLogs: { enabled: true },
};

Next Steps