Skip to main content
This document outlines important security considerations and best practices when using the Auth0 Next.js SDK.

Cookies and Security

The SDK uses HTTP-only, secure cookies for session management with built-in protections against common attacks. All cookies are automatically configured with security flags:
HttpOnly
boolean
default:"true"
Always enabled. Prevents client-side JavaScript from accessing the cookie, reducing the attack surface for XSS (Cross-Site Scripting) attacks.This flag cannot be disabled.
SameSite
string
default:"Lax"
Set to Lax by default to help mitigate CSRF (Cross-Site Request Forgery) attacks.
  • Lax: Cookies sent with top-level navigation and same-site requests
  • Strict: Cookies only sent with same-site requests
  • None: Cookies sent with all requests (requires Secure flag)
Learn more: Browser Behavior Changes: What Developers Need to Know
Secure
boolean
Automatically set to true when APP_BASE_URL uses https://.Ensures cookies are only transmitted over HTTPS connections, preventing interception over insecure networks.Important: When using dynamic base URLs in production, the SDK enforces secure: true. Explicitly setting secure: false will throw InvalidConfigurationError.
Customize cookie settings via configuration or environment variables:
import { Auth0Client } from "@auth0/nextjs-auth0/server";

export const auth0 = new Auth0Client({
  session: {
    cookie: {
      domain: ".example.com",     // Cookie domain
      path: "/",                  // Cookie path
      secure: true,               // HTTPS only
      sameSite: "lax",            // CSRF protection
      transient: false            // Persist across browser sessions
    }
  }
});
Environment variables:
AUTH0_COOKIE_DOMAIN=.example.com
AUTH0_COOKIE_PATH=/
AUTH0_COOKIE_SECURE=true
AUTH0_COOKIE_SAME_SITE=lax
AUTH0_COOKIE_TRANSIENT=false
Never disable HttpOnly. The SDK always sets httpOnly: true and does not allow it to be disabled.

Session Security

Session Encryption

All session data is encrypted using AES-256-GCM before being stored in cookies. Generate a strong secret:
openssl rand -hex 32
AUTH0_SECRET=your-64-character-hex-string
Keep your AUTH0_SECRET secure:
  • Never commit to source control
  • Use different secrets for each environment
  • Rotate secrets periodically
  • Minimum 32 bytes (64 hex characters)

Rolling Sessions

The SDK uses rolling sessions by default, which automatically extends the session expiry on each request. Benefits:
  • Active users stay logged in
  • Idle sessions expire
  • Reduces re-authentication friction
Security consideration: Rolling sessions generate a Set-Cookie header on every request that touches the session. This means:
Never cache responses that read the session, even if the content appears safe to cache. The Set-Cookie header contains sensitive session data and must not be cached by CDNs or edge networks.
// These functions read the session and should NOT be cached:
await auth0.getSession();
await auth0.getAccessToken();

Session Duration

Configure session timeouts to balance security and user experience:
const auth0 = new Auth0Client({
  session: {
    rolling: true,
    rollingDuration: 86400,  // 24 hours - session extends on activity
    absoluteDuration: 604800 // 7 days - maximum session lifetime
  }
});
  • Rolling duration: How long the session lasts without activity
  • Absolute duration: Maximum session lifetime regardless of activity
Best practice: Set absoluteDuration to match your security requirements. High-security applications should use shorter durations (e.g., 1-4 hours).

Stateful Sessions

For enhanced security or large sessions, store session data server-side:
import { Auth0Client, AbstractSessionStore } from "@auth0/nextjs-auth0/server";
import { Redis } from "ioredis";

class RedisSessionStore extends AbstractSessionStore {
  private redis = new Redis();
  
  async get(sid: string) {
    const data = await this.redis.get(`session:${sid}`);
    return data ? JSON.parse(data) : null;
  }
  
  async set(sid: string, session: any, ttl: number) {
    await this.redis.setex(`session:${sid}`, ttl, JSON.stringify(session));
  }
  
  async delete(sid: string) {
    await this.redis.del(`session:${sid}`);
  }
}

const auth0 = new Auth0Client({
  sessionStore: new RedisSessionStore()
});
Benefits:
  • No 4KB cookie size limit
  • Server-side session revocation
  • Reduced client-side data exposure
See Database sessions for details.

Token Security

Access Token Storage

Access tokens are stored encrypted in the session cookie (or session store).
Never expose access tokens to the client unless absolutely necessary. Use server-side API routes to call external APIs:
// app/api/data/route.ts
import { auth0 } from "@/lib/auth0";

export async function GET() {
  const { token } = await auth0.getAccessToken();
  
  const response = await fetch("https://api.example.com/data", {
    headers: { Authorization: `Bearer ${token}` }
  });
  
  return response;
}

Refresh Token Security

Refresh tokens are long-lived credentials that can obtain new access tokens. Best practices:
Only request refresh tokens when needed:
const auth0 = new Auth0Client({
  authorizationParameters: {
    scope: "openid profile email offline_access"
  }
});
Configure in Auth0 Dashboard:
  1. Go to Applications > Your App > Advanced Settings
  2. Enable Refresh Token Rotation
  3. Enable Refresh Token Reuse Detection
This issues a new refresh token on each use and invalidates the old one, detecting token theft.
Configure in Auth0 Dashboard:
  1. Go to Applications > Your App > Advanced Settings
  2. Set Refresh Token Expiration based on security requirements
  3. Enable Inactivity Expiration to expire tokens after periods of inactivity

Token Refresh Buffer

Refresh tokens proactively to avoid expiration mid-request:
const auth0 = new Auth0Client({
  tokenRefreshBuffer: 60 // Refresh 60 seconds before expiry
});

XSS Protection

Error Message Handling

Critical: OAuth errors may contain reflected user input via the error and error_description query parameters.Never render these values directly without escaping to prevent XSS attacks.
import { OAuth2Error } from "@auth0/nextjs-auth0/errors";

try {
  await auth0.handleCallback(request);
} catch (error) {
  if (error instanceof OAuth2Error) {
    // ❌ UNSAFE - may contain malicious input
    return <div>{error.message}</div>;
    
    // ✅ SAFE - escaped by template engine
    return <div>{escapeHtml(error.message)}</div>;
    
    // ✅ SAFE - React automatically escapes
    return <div>{error.message}</div>;
  }
}
See the OWASP XSS Prevention Cheat Sheet for proper escaping techniques.

Content Security Policy

Implement CSP headers to mitigate XSS:
// middleware.ts
export async function middleware(request: NextRequest) {
  const response = await auth0.middleware(request);
  
  response.headers.set(
    "Content-Security-Policy",
    "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
  );
  
  return response;
}

CSRF Protection

The SDK provides built-in CSRF protection through:
  1. SameSite cookies: Default Lax setting prevents CSRF attacks
  2. State parameter: Validated on OAuth callback
  3. Transaction cookies: Bind authentication state to the session

Custom CSRF Protection

For additional protection on custom routes:
import { randomBytes } from "crypto";

// Generate CSRF token
const csrfToken = randomBytes(32).toString("hex");

// Store in session
const session = await auth0.getSession();
session.csrfToken = csrfToken;

// Validate on form submission
if (formToken !== session.csrfToken) {
  throw new Error("Invalid CSRF token");
}

Input Validation

Always validate user inputs, especially redirect URLs, to prevent open redirect vulnerabilities.

Safe Redirect Handling

// ❌ UNSAFE - arbitrary redirect
const returnTo = request.nextUrl.searchParams.get("returnTo");
redirect(returnTo);

// ✅ SAFE - validate against allowlist
const returnTo = request.nextUrl.searchParams.get("returnTo");
const safeUrls = ["/dashboard", "/profile", "/settings"];

if (safeUrls.includes(returnTo)) {
  redirect(returnTo);
} else {
  redirect("/"); // Default safe redirect
}

// ✅ SAFE - use relative URLs only
if (returnTo?.startsWith("/") && !returnTo.startsWith("//")) {
  redirect(returnTo);
} else {
  redirect("/");
}

beforeSessionSaved Hook Validation

const auth0 = new Auth0Client({
  beforeSessionSaved: async (session) => {
    // Validate user metadata before persisting
    if (session.user.email && !isValidEmail(session.user.email)) {
      throw new Error("Invalid email");
    }
    return session;
  }
});

DPoP (Demonstrating Proof-of-Possession)

DPoP binds access tokens to cryptographic key pairs, preventing token theft and replay attacks.

Enable DPoP

import { Auth0Client } from "@auth0/nextjs-auth0/server";
import { generateKeyPair } from "oauth4webapi";

const dpopKeyPair = await generateKeyPair("ES256");

const auth0 = new Auth0Client({
  useDPoP: true,
  dpopKeyPair
});
Benefits:
  • Prevents token theft (stolen tokens are useless without private key)
  • Prevents replay attacks (proof is bound to request)
  • Enhanced security for high-value transactions
Key management:
Protect DPoP private keys:
  • Store in environment variables or secrets manager
  • Never expose to client-side code
  • Rotate keys periodically
  • Use hardware security modules (HSM) for production
AUTH0_DPOP_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----"

AUTH0_DPOP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQ...
-----END PRIVATE KEY-----"
See DPoP Examples for details.

Caching Security

Critical: Never cache responses that require authentication or touch the session.
Many hosting providers (Vercel, Netlify) cache responses at the edge. Caching authenticated responses can:
  1. Expose session cookies via cached Set-Cookie headers
  2. Leak user data to other users
  3. Bypass authentication checks

What NOT to Cache

Never cache responses from:
  • auth0.getSession()
  • auth0.getAccessToken()
  • useUser() hook
  • Any route that checks authentication
  • Rolling session responses (contain Set-Cookie)

Safe Caching

Only cache:
  • Public, unauthenticated content
  • Static assets
  • API responses that don’t require authentication
// Safe - public data
export async function GET() {
  const data = await fetchPublicData();
  return Response.json(data, {
    headers: {
      "Cache-Control": "public, max-age=3600"
    }
  });
}

// Unsafe - authenticated data
export async function GET() {
  const session = await auth0.getSession();
  return Response.json(session, {
    headers: {
      "Cache-Control": "private, no-cache, no-store, must-revalidate"
    }
  });
}

Dynamic Base URLs

For preview environments (Vercel, Netlify), the SDK can infer the base URL from the request host.
Security note: The Host header is untrusted user input. Auth0’s Allowed Callback URLs act as the security boundary. If the inferred host is not registered in Auth0, the authorization request will be rejected.
// Dynamic base URL (preview environments)
const auth0 = new Auth0Client();
// Infers from request.headers.get("host")
Security enforcements:
  1. Secure cookies enforced: When using dynamic base URLs in production, secure: false throws InvalidConfigurationError
  2. Callback URL validation: Auth0 validates the callback URL against your registered URLs
  3. Protocol inference: HTTPS is assumed for security
Best practice: Use static APP_BASE_URL in production:
# Production
APP_BASE_URL=https://app.example.com

# Preview (omit or leave empty)
# APP_BASE_URL=

Logout Security

OIDC Logout

The SDK supports OIDC logout with optional id_token_hint:
const auth0 = new Auth0Client({
  logoutStrategy: "oidc",
  includeIdTokenHintInOIDCLogoutUrl: true // Default, recommended
});
Security tradeoff:
includeIdTokenHintInOIDCLogoutUrlSecurity BenefitPrivacy Impact
true (default)Better DoS protectionID token in logout URL (PII in logs)
falseNo PII in logout URLsReduced DoS protection
See OIDC logout privacy configuration for details.

Backchannel Logout

Implement backchannel logout for server-initiated session termination:
  1. Configure backchannel logout URI in Auth0 Dashboard:
    • https://yourdomain.com/auth/backchannel-logout
  2. The SDK automatically handles logout token validation
  3. Sessions are terminated immediately
See Auth0 Back-Channel Logout for details.

Vulnerability Reporting

Do not report security vulnerabilities on the public GitHub issue tracker.
Please report security issues through Auth0’s Responsible Disclosure Program.

Security Checklist

  • Use strong AUTH0_SECRET (32+ bytes, hex-encoded)
  • Rotate secrets periodically
  • Never commit secrets to source control
  • Use different secrets per environment
  • Enable Refresh Token Rotation in Auth0 Dashboard
  • Store tokens server-side only
  • Request minimum required scopes
  • Use token refresh buffer
  • Implement token refresh error handling
  • Consider DPoP for high-security apps
  • Validate all redirect URLs
  • Escape OAuth error messages
  • Sanitize user inputs in hooks
  • Implement CSRF tokens for custom forms
  • Use allowlists for redirects
  • Never cache authenticated responses
  • Check hosting provider caching rules
  • Set appropriate Cache-Control headers
  • Verify Set-Cookie not cached
  • Register all callback URLs in Auth0 Dashboard
  • Enable Refresh Token Rotation
  • Configure appropriate token lifetimes
  • Enable MFA for sensitive operations
  • Review Auth0 logs regularly

Additional Resources

Build docs developers (and LLMs) love