Skip to main content

How It Works

nauth-toolkit lives inside your backend. You configure it once — it gives you services you call from your routes. No separate process, no external API, no SDK calls over the network.

Where It Lives

Your routes call nauth services. nauth reads and writes to your database. Your frontend talks to your backend as normal — nothing in the middle.

Authentication only — not authorization

nauth-toolkit verifies identity and issues tokens. It does not check permissions, roles, or access control. Authorization is your responsibility — use your own guards, policies, or an RBAC library on top.

What You Configure

Two things, once, at startup:

What You Write

Backend — thin route handlers

You define endpoints that map to nauth service calls. Your route handlers are thin — nauth handles everything behind the service call (password hashing, JWT issuance, session management, rate limiting, audit logging):

@Post('signup')
@Public()
async signup(@Body() body: SignupDTO): Promise<AuthResponseDTO> {
return this.authService.signup(body);
}

@Post('login')
@Public()
async login(@Body() body: LoginDTO): Promise<AuthResponseDTO> {
return this.authService.login(body);
}

@Get('profile')
@UseGuards(AuthGuard)
profile(@CurrentUser() user: NAuthUser): NAuthUser {
return user;
}

You choose which endpoints to expose. The same authService.signup() and authService.login() calls work identically across all three frameworks.

ServiceWhat you get
AuthServiceSignup, login, logout, password reset, token refresh, email/phone verification
MFAServiceEnroll and verify TOTP, SMS, email, and passkey methods
SocialAuthServiceGoogle, Apple, Facebook — redirect flows and native mobile token verification

Frontend — SDK with challenge routing

The client SDK (@nauth-toolkit/client) handles token storage, CSRF, and auth state. Your frontend calls SDK methods and responds to challenges:

// Login — SDK returns tokens or a challenge
const result = await auth.login(email, password);

// Challenge? Route the user to the right screen
if (result.challengeName) {
// 'MFA_REQUIRED', 'VERIFY_EMAIL', 'MFA_SETUP_REQUIRED', etc.
navigateToChallenge(result);
return;
}

// No challenge — user is authenticated
navigateToDashboard();

Challenges can chain: signup may require email verification → then MFA setup → then login MFA. The SDK tracks the session across steps. When the final challenge is resolved, tokens are issued.

Challenge System — all challenge types and how to resolve them

Request Processing Pipeline

Every request passes through a fixed handler chain before reaching your route. The order is the same across all frameworks — only the registration mechanism differs:

OrderHandlerResponsibility
1ClientInfoHandlerExtracts IP, user-agent, device token, and geo data from the request. Initializes the AsyncLocalStorage context that all downstream handlers depend on.
2CsrfHandlerValidates the CSRF token when token delivery uses cookies or hybrid mode. Skipped for JSON-only delivery.
3AuthHandlerValidates the JWT access token and attaches the authenticated user to the request context. Routes marked @Public() skip validation.
4TokenDeliveryHandlerResponse interceptor — rewrites the outgoing response to deliver tokens via Set-Cookie headers (cookie/hybrid mode) or leaves them in the JSON body (JSON mode).
Framework specifics
  • NestJS — Handlers 1-3 run as global guards (NAuthContextGuardCsrfGuard); handler 4 runs as a global interceptor (CookieTokenInterceptor).
  • Express — Handlers 1-3 register as middleware via app.use(); handler 4 registers via registerResponseInterceptor().
  • Fastify — Handlers 1-3 register as onRequest/preHandler hooks; handler 4 registers as an onSend hook. Each hook restores the AsyncLocalStorage context from the request object.

Framework Support

nauth-toolkit has a framework-agnostic core. The same services work across all three integrations:

FrameworkHow you add it
NestJSImport AuthModule.forRoot() — guards and decorators are wired automatically
ExpressCall NAuth.create() with ExpressAdapter — middleware registered on your app
FastifyCall NAuth.create() with FastifyAdapter — hooks registered on your instance

Quick Start — get running in minutes

What's Next