next-api-layerNext API Layer
DocumentationAPI ReferenceExamples
next-api-layerNext API Layer

Production-grade API layer for Next.js with external JWT backends.

Documentation

  • Introduction
  • Installation
  • Quick Start
  • API Reference

Resources

  • Examples
  • Proxy
  • API Client
  • AuthProvider

Community

  • GitHub
  • Issues
  • Discussions
  • Contact

© 2026 Next API Layer. All rights reserved.

Created by
Documentation

Getting Started

  • Introduction
  • Installation
  • Quick Start

Core Concepts

  • How It Works
  • Token Management
  • Guest Tokens

Configuration

  • Auth Proxy
  • Proxy Handler
  • API Client
  • Security
  • i18n Integration

Client Side

  • AuthProvider
  • useAuth Hook

API Reference

  • API Reference
  • Types

Examples

  • Examples
  • Authentication Patterns
  • Role-Based Access
  • API Routes
  • Forms
  • Data Fetching
Changelog

Getting Started

  • Introduction
  • Installation
  • Quick Start

Core Concepts

  • How It Works
  • Token Management
  • Guest Tokens

Configuration

  • Auth Proxy
  • Proxy Handler
  • API Client
  • Security
  • i18n Integration

Client Side

  • AuthProvider
  • useAuth Hook

API Reference

  • API Reference
  • Types

Examples

  • Examples
  • Authentication Patterns
  • Role-Based Access
  • API Routes
  • Forms
  • Data Fetching
Changelog

Security

next-api-layer includes a comprehensive security system with multiple layers of protection. This page covers all security features and their configuration.

Overview#

The security system provides:

  • CSRF Protection - Prevents cross-site request forgery attacks
  • Rate Limiting - Protects against brute force and DDoS attacks
  • XSS Sanitization - Cleans malicious content from responses
  • Audit Logging - Tracks security events for compliance and debugging

Security hardening in v0.3.0#

v0.3.0 is a security-focused release. If you are upgrading from 0.2.x, note:

  • CSRF tokens are HMAC-signed and verified. Double-submit no longer trusts a bare cookie/header match — the signature is checked on every state-changing request, so a token planted on a sibling subdomain can't be forged. Token format is now <hmac>.<randomValue>.
  • Web Crypto is required for CSRF. Token and secret generation use crypto.subtle / crypto.getRandomValues and throw if unavailable (no insecure Math.random() fallback). Works on Node.js 18+ and Edge runtimes.
  • Spoofed internal headers are stripped. Inbound x-auth-user, x-refreshed-token, and x-locale headers are removed before the proxy sets its own verified values, so a client can't impersonate a user or override the locale.
  • An auth redirect can no longer be bypassed by i18n. A terminal auth response (a redirect to /login, or a 401) now takes precedence over an i18n forward.
  • The allow-list sanitizer cleans attributes. Allowed tags can no longer carry onerror=, style=, or javascript: / data: URLs.
  • Rate-limit prefetch skip is GET/HEAD-only, and the proxy 502 path no longer leaks error.message.

Breaking: if you call createCsrfValidator directly, its methods are now async — await csrf.validateRequest(req). Set a stable csrf.secret in production. See the changelog for full details.

CSRF Protection#

Cross-Site Request Forgery (CSRF) protection prevents malicious sites from making requests on behalf of authenticated users.

Configuration#

TypeScript
Loading...

secret (strongly recommended). The key used to HMAC-sign CSRF tokens. If you omit it, a random per-process secret is generated and a warning is logged — tokens then fail to validate across restarts or multiple instances. Set a stable value (e.g. from an environment variable) in production. Web Crypto must be available or token generation throws.

When CSRF is enabled, createAuthProxy automatically sets the signed __csrf cookie on responses for authenticated requests — you don't mint tokens yourself.

Strategies#

fetch-metadata

Uses the Sec-Fetch-Site header sent by modern browsers (no token needed).

TypeScript
Loading...

The decision is based entirely on Sec-Fetch-Site:

  • same-origin → always allowed
  • same-site → allowed only when trustSameSite: true (rejected otherwise)
  • none (direct navigation, e.g. a bookmark or typed URL) → allowed for safe methods only
  • cross-site, an unknown value, or a missing header → rejected for state-changing methods

Pros: No token management needed Cons: Relies on the Sec-Fetch-Site header; on its own it rejects clients that don't send it, so pair it with double-submit via the both strategy for older clients.

double-submit

Classic double-submit cookie pattern.

TypeScript
Loading...

How it works:

  1. When CSRF is enabled, the proxy sets a signed __csrf cookie (<hmac>.<randomValue>) on authenticated responses.
  2. The client reads that cookie and echoes it back in the x-csrf-token header on state-changing requests.
  3. The server checks that the cookie and header match (constant-time) and verifies the HMAC signature — so a forged or tampered token is rejected even when the cookie/header pair matches.

Client-side usage:

TypeScript
Loading...

both

Combines both strategies for maximum compatibility.

TypeScript
Loading...

Validates:

  1. First checks Sec-Fetch-* headers (if available)
  2. Then falls back to double-submit validation

Recommended for production.

Trust Same-Site#

TypeScript
Loading...

Controls how same-site requests are treated. same-origin requests are always trusted; same-site requests (e.g. from a sibling subdomain) are rejected by default to avoid subdomain-takeover risk. Set trustSameSite: true to accept them.

Ignore Methods#

TypeScript
Loading...

Safe methods that don't need CSRF protection.

Rate Limiting#

Protects your API from abuse by limiting the number of requests per time window.

Configuration#

TypeScript
Loading...

Options#

windowMs

Time window in milliseconds.

TypeScript
Loading...

maxRequests

Maximum requests allowed per window.

TypeScript
Loading...

keyFn

Function to generate rate limit key. Defaults to IP address.

TypeScript
Loading...

skipRoutes

Routes to exclude from rate limiting.

TypeScript
Loading...

skipPrefetch

Whether to skip rate-limit counting for Next.js <Link> prefetch requests. Defaults to true. Prefetches are background navigations triggered by the router, so counting them would burn through a user's quota before they ever click. They are detected via the next-router-prefetch, sec-purpose, purpose / x-purpose, and x-moz request headers.

TypeScript
Loading...

Set it to false if you want prefetch requests to count against the limit.

Since v0.3.0, the skip applies to safe methods only (GET / HEAD). Only those are ever prefetched, so a forged prefetch header on a POST/PUT/etc. can no longer be used to slip past the limiter.

ipHeaders

Ordered list of headers used to resolve the real client IP for the default rate-limit key. The first header that contains a value wins. Defaults to:

TypeScript
Loading...

This gives correct client identification behind CDNs and reverse proxies. For x-forwarded-for, the first (left-most) address — the original client — is used. Customize the order to match your infrastructure:

TypeScript
Loading...

The getClientIp(req, headerPriority?) helper that powers this is also exported, so you can reuse the same resolution logic in custom keyFn implementations or your own middleware. isPrefetchRequest(req) and DEFAULT_IP_HEADERS are exported as well.

onRateLimited

Custom response when rate limited.

TypeScript
Loading...

Headers#

Rate limit information is included in response headers:

Loading...

XSS Sanitization#

The API client automatically sanitizes outgoing request bodies (the data you send with POST, PUT, and PATCH) to prevent stored/persisted XSS. It follows the same philosophy as DOMPurify: remove dangerous nodes, do not over-escape plain text. By cleaning data before it reaches your backend, anything that gets persisted stays safe to render later (including inside dangerouslySetInnerHTML, v-html, [innerHTML]).

Configuration#

TypeScript
Loading...

Modes#

| Mode | Behavior | Use case | |---|---|---| | strip (default) | Removes well-formed HTML tags; preserves plain text chars (', /, `, =, bare <, >, &). | Default for request bodies later rendered via JSX / templates. | | escape | Escapes <, >, &, " to HTML entities. Leaves other chars untouched. | Strings going into raw HTML contexts (dangerouslySetInnerHTML, email templates). | | allowList | Keeps only tags in allowedTags, strips the rest. | Rich-text / CMS content. |

Why strip is the new default#

Earlier versions defaulted to escape with an aggressive entity set (/, ', `, = all escaped). That corrupted text like O'Reilly's Books into O&#x27;Reilly&#x27;s Books in the UI. The current defaults mirror how real-world HTML sanitizers (DOMPurify, sanitize-html) behave: they operate at the tag level, not at the character level.

How It Works#

All string values in the request body are sanitized before the request is sent:

TypeScript
Loading...

Allowed Tags (for allowList mode)#

TypeScript
Loading...

In allowList mode the attributes on the tags you allow are also cleaned: inline event handlers (onerror=, onclick=, …), style=, and any attribute whose value uses a javascript:, vbscript:, or data: scheme are stripped. So <a href="javascript:alert(1)"> keeps the <a> but drops the dangerous href.

Skip Fields#

Fields that should not be sanitized (use with caution):

TypeScript
Loading...

Audit Logging#

Track security-relevant events for compliance, debugging, and monitoring.

Configuration#

TypeScript
Loading...

Event Types#

EventDescription
auth:successUser successfully authenticated
auth:failAuthentication failed
auth:refreshToken was refreshed
auth:guestGuest token was created
access:deniedAccess to protected route denied
csrf:failCSRF validation failed
rateLimit:exceededRate limit exceeded
errorAn error occurred

Event Structure#

TypeScript
Loading...

Logger Examples#

Console Logger

TypeScript
Loading...

Database Logger

TypeScript
Loading...

External Service

TypeScript
Loading...

Security Best Practices#

Cookie Configuration#

TypeScript
Loading...

Environment-Specific Settings#

TypeScript
Loading...

Protect Sensitive Routes#

TypeScript
Loading...

Rate Limit Sensitive Endpoints#

A single createAuthProxy instance has one global window (windowMs / maxRequests); there is no per-route limit map. To stop a burst on one endpoint from sharing a counter with the rest of the app, key the limiter by path so each (ip, path) pair gets its own bucket — or enforce tighter limits inside the route handler itself.

TypeScript
Loading...

Security Headers#

next-api-layer does not inject HTTP security headers for you — that stays under your control. Set them centrally in next.config.js (applies to every response), or per-request in an afterAuth hook:

TypeScript
Loading...