npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@csrf-armor/nextjs

v1.4.1

Published

CSRF protection middleware for Next.js applications

Readme

@csrf-armor/nextjs

CI npm version License: MIT TypeScript Next.js

Complete CSRF protection for Next.js applications with App Router and Pages Router support, middleware integration, and React hooks.

Built for Next.js 12+ with support for both App Router and Pages Router, Edge Runtime compatibility, and modern React patterns.

Contents

✨ Features

  • 🛡️ Multiple Security Strategies - Choose from 5 different CSRF protection methods
  • 🔄 App Router & Pages Router - Full support for both Next.js routing systems
  • 🪝 React Hooks - useCsrf hook for seamless client-side integration
  • Edge Runtime Compatible - Works in Vercel Edge Runtime and serverless environments
  • 🎯 TypeScript First - Fully typed with comprehensive TypeScript support
  • 📱 SSR & Client-Side - Full support for server-side and client-side rendering

🚀 Quick Start

The middleware setup works for both App Router and Pages Router. Provider setup differs:

  • App Router: Use app/layout.tsx with CsrfProvider.
  • Pages Router: Use _app.tsx with CsrfProvider.

1. Installation

npm install @csrf-armor/nextjs
# or
yarn add @csrf-armor/nextjs
# or
pnpm add @csrf-armor/nextjs

2. Environment Setup

Add to your .env.local:

# Generate with: openssl rand -base64 32
CSRF_SECRET=your-super-secret-csrf-key-min-32-chars-long

⚠️ Security Warning: Never use a default or weak secret in production!

3. Create Middleware

Create middleware.ts in your project root:

import {NextResponse} from 'next/server';
import type {NextRequest} from 'next/server';
import {createCsrfMiddleware} from '@csrf-armor/nextjs';

// Validate secret in production
if (process.env.NODE_ENV === 'production' && !process.env.CSRF_SECRET) {
    throw new Error('CSRF_SECRET environment variable is required in production');
}

const csrfProtect = createCsrfMiddleware({
    strategy: 'signed-double-submit',
    secret: process.env.CSRF_SECRET!,
    cookie: {
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'lax' // Use 'strict' for higher security if cross-origin not needed
    }
});

export async function middleware(request: NextRequest) {
    const response = NextResponse.next();
    const result = await csrfProtect(request, response);

    if (!result.success) {
        // Security logging
        console.warn('CSRF validation failed:', {
            url: request.url,
            method: request.method,
            reason: result.reason,
            ip: request.ip || 'unknown',
            userAgent: request.headers.get('user-agent') || 'unknown',
        });

        return NextResponse.json(
            {error: 'CSRF validation failed'},
            {status: 403}
        );
    }

    return result.response;
}

4. Context Provider Setup (App Router)

Wrap your app with the CSRF provider in app/layout.tsx (Next.js 13+ App Router):

// app/layout.tsx
import {CsrfProvider} from '@csrf-armor/nextjs/client';
import type {Metadata} from 'next';

export const metadata: Metadata = {
    title: 'Your App',
    description: 'Your app description',
};

export default function RootLayout({children}: {
    children: React.ReactNode;
}) {
    return (
        <html lang="en">
        <body>
        <CsrfProvider>{children}</CsrfProvider>
        </body>
        </html>
    );
}

4b. Context Provider Setup (Pages Router)

Wrap your app in _app.tsx (Next.js 12+ Pages Router):

// pages/_app.tsx
import {CsrfProvider} from '@csrf-armor/nextjs/client';

export default function MyApp({Component, pageProps}) {
    return (
        <CsrfProvider>
            <Component {...pageProps} />
        </CsrfProvider>
    );
}

5. Usage in Components

'use client';
import {useCsrf} from '@csrf-armor/nextjs/client';
import {useState} from 'react';

export function ContactForm() {
    const {csrfToken, csrfFetch} = useCsrf();
    const [message, setMessage] = useState('');

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        const formData = new FormData(e.currentTarget);
        const response = await csrfFetch('/api/contact', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                name: formData.get('name'),
                email: formData.get('email'),
                message: formData.get('message'),
            }),
        });
    };

    return (
        <form onSubmit={handleSubmit} className="space-y-4">
            <div>
                <textarea
                    name="message"
                    placeholder="Your Message"
                    required
                    rows={4}
                    className="w-full p-2 border rounded"
                />
            </div>
            <button
                type="submit"
                disabled={!csrfToken}
                className="bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50">
                {isSubmitting ? 'Sending...' : 'Send Message'}
            </button>
        </form>
    );
}

6. API Route Example

// app/api/your-route
import {NextRequest, NextResponse} from 'next/server';

export async function POST(request: NextRequest) {
    // CSRF validation happens automatically in middleware
}

🔄 Routing System Setup

CSRF Armor supports both Next.js routing systems using the same root middleware.ts file. See Quick Start.

Universal Middleware (Both App Router & Pages Router)

// middleware.ts (project root) - works for both routing systems
import {NextResponse} from 'next/server';
import type {NextRequest} from 'next/server';
import {createCsrfMiddleware} from '@csrf-armor/nextjs';

const csrfProtect = createCsrfMiddleware({
    strategy: 'signed-double-submit',
    secret: process.env.CSRF_SECRET!,
});

export async function middleware(request: NextRequest) {
    const response = NextResponse.next();
    const result = await csrfProtect(request, response);

    if (!result.success) {
        return NextResponse.json(
            {error: 'CSRF validation failed'},
            {status: 403}
        );
    }

    return result.response;
}

export const config = {
    matcher: [
        // Protect all routes except static files
        '/((?!_next/static|_next/image|favicon.ico).*)',
    ],
};

App Router Provider Setup

// app/layout.tsx
import {CsrfProvider} from '@csrf-armor/nextjs/client';

export default function RootLayout({children}: {
    children: React.ReactNode;
}) {
    return (
        <html lang="en">
        <body>
        <CsrfProvider>{children}</CsrfProvider>
        </body>
        </html>
    );
}

Pages Router Provider Setup

// pages/_app.tsx
import type {AppProps} from 'next/app';
import {CsrfProvider} from '@csrf-armor/nextjs/client';

export default function App({Component, pageProps}: AppProps) {
    return (
        <CsrfProvider>
            <Component {...pageProps} />
        </CsrfProvider>
    );
}

Using Hooks in Both Routing Systems

The React hooks work identically in both App Router and Pages Router:

'use client'; // Only needed in App Router

import {useCsrf} from '@csrf-armor/nextjs/client';

export function ContactForm() {
    const {csrfToken, csrfFetch} = useCsrf();

    const handleSubmit = async (e: React.FormEvent) => {
        //...
        try {
            const response = await csrfFetch('/api/contact', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({message: 'Hello'}),
            });

            if (response.ok) {
                console.log('Success!');
            }
        } catch (error) {
            console.error('Error:', error);
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <input name="message" placeholder="Your message" />
            <button type="submit">Send</button>
        </form>
    );
}

🛡️ Security Strategies

Choose the strategy that best fits your security and performance requirements:

| Strategy | Security | Performance | Best For | Setup Complexity | |----------------------------|----------|-------------|------------------|------------------| | Signed Double Submit ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Most web apps | Medium | | Double Submit | ⭐ | ⭐⭐⭐⭐⭐ | Local development | Easy | | Signed Token | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | APIs, SPAs | Medium | | Origin Check | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Known origins | Easy | | Hybrid | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | Maximum security | Hard |

Signed Double Submit (Recommended)

const csrfProtect = createCsrfMiddleware({
    strategy: 'signed-double-submit',
    secret: process.env.CSRF_SECRET!,
});

How it works:

  • Client receives unsigned token in response header and accessible cookie
  • Server stores signed token in httpOnly cookie
  • Client submits unsigned token, server verifies against signed cookie
  • Combines cryptographic protection with double-submit pattern

Best for: E-commerce, financial services, general web applications

Double Submit Cookie

const csrfProtect = createCsrfMiddleware({
    strategy: 'double-submit',
});

How it works:

  • Same token stored in cookie and sent in header/form
  • Relies on Same-Origin Policy for protection

Best for: Local development (Not recommended for production)

Signed Token

const csrfProtect = createCsrfMiddleware({
    strategy: 'signed-token',
    secret: process.env.CSRF_SECRET!,
    token: {expiry: 3600}, // 1 hour
});

How it works:

  • HMAC-signed tokens with expiration timestamps
  • Stateless validation using cryptographic signatures

Best for: APIs, SPAs, microservices

Origin Check

const csrfProtect = createCsrfMiddleware({
    strategy: 'origin-check',
    allowedOrigins: [
        'https://yourdomain.com',
        'https://www.yourdomain.com',
    ],
});

How it works:

  • Validates Origin/Referer headers against whitelist
  • Lightweight validation with minimal overhead

Best for: Mobile app backends, known client origins

Hybrid Protection

const csrfProtect = createCsrfMiddleware({
    strategy: 'hybrid',
    secret: process.env.CSRF_SECRET!,
    allowedOrigins: ['https://yourdomain.com'],
});

How it works:

  • Combines signed token validation with origin checking
  • Multiple layers of protection for maximum security

Best for: Banking, healthcare, enterprise applications


⚙️ Configuration

Complete Configuration Reference

interface CsrfConfig {
    strategy?: 'double-submit' | 'signed-double-submit' | 'signed-token' | 'origin-check' | 'hybrid';
    secret?: string;                    // Required for signed strategies

    token?: {
        expiry?: number;                  // Token expiry in seconds (default: 3600)
        headerName?: string;              // Header name (default: 'x-csrf-token')
        fieldName?: string;               // Form field name (default: 'csrf_token')
    };

    cookie?: {
        name?: string;                    // Cookie name (default: 'csrf-token')
        secure?: boolean;                 // Secure flag (default: true in production)
        httpOnly?: boolean;               // HttpOnly flag (default: false)
        sameSite?: 'strict' | 'lax' | 'none'; // SameSite (default: 'lax')
        path?: string;                    // Path (default: '/')
        domain?: string;                  // Domain (optional)
        maxAge?: number;                  // Max age in seconds (optional)
    };

    allowedOrigins?: string[];          // Allowed origins for origin-check
    excludePaths?: string[];            // Paths to exclude from protection
    skipContentTypes?: string[];        // Content types to skip
}

Environment-Specific Configuration

// Development configuration
const developmentConfig = {
    strategy: 'double-submit' as const,
    cookie: {
        secure: false,      // Allow HTTP in development
        sameSite: 'lax' as const
    }
};

// Production configuration
const productionConfig = {
    strategy: 'signed-double-submit' as const,
    secret: process.env.CSRF_SECRET!,
    cookie: {
        secure: true,       // HTTPS only
        sameSite: 'strict' as const,
        domain: '.yourdomain.com'
    }
};

const csrfProtect = createCsrfMiddleware(
    process.env.NODE_ENV === 'production'
        ? productionConfig
        : developmentConfig
);

Path Exclusions

const csrfProtect = createCsrfMiddleware({
    strategy: 'signed-double-submit',
    secret: process.env.CSRF_SECRET!,
    excludePaths: [
        '/api/webhooks',     // External webhooks
        '/api/public',       // Public API endpoints
        '/health',           // Health checks
        '/api/auth/callback' // Auth callbacks
    ],
});

🪝 React Hooks API

CsrfProvider

The context provider that manages CSRF state across your application.

interface CsrfProviderProps {
    children: React.ReactNode;
    config?: CsrfClientConfig;
}

interface CsrfClientConfig {
    cookieName?: string;    // Cookie name to read token from (default: 'csrf-token')
    headerName?: string;    // Header name to send token in (default: 'x-csrf-token')
    autoRefresh?: boolean;  // Auto-refresh on focus/visibility (default: true)
}

Features:

  • ✅ Event-driven updates (no polling)
  • ✅ Automatic token refresh from response headers
  • ✅ Shared state across components
  • ✅ Performance optimized with React.memo

Usage:

<CsrfProvider config={{
    cookieName: 'my-csrf',
    headerName: 'X-My-CSRF',
    autoRefresh: true
}}>
    <App/>
</CsrfProvider>

useCsrf Hook

Main hook for accessing CSRF functionality.

const {csrfToken, csrfFetch, updateToken} = useCsrf();

Returns:

  • csrfToken: string | null - Current CSRF token
  • csrfFetch: (input, init?) => Promise<Response> - Fetch with automatic CSRF headers
  • updateToken: () => void - Manually refresh token

🔒 Security Best Practices

1. Strong Secret Management

# Generate a strong secret
openssl rand -base64 32

# Or using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
// Validate secret at startup
if (process.env.NODE_ENV === 'production') {
    const secret = process.env.CSRF_SECRET;
    if (!secret || secret.length < 32) {
        throw new Error('CSRF_SECRET must be at least 32 characters in production');
    }
}

2. Cookie Security Configuration

const csrfProtect = createCsrfMiddleware({
    strategy: 'signed-double-submit',
    secret: process.env.CSRF_SECRET!,
    cookie: {
        secure: process.env.NODE_ENV === 'production', // HTTPS only in production
        sameSite: 'strict',    // Strictest protection
        httpOnly: false,       // Required for client access
        path: '/',
        maxAge: 60 * 60 * 24,  // 24 hours
        // For subdomains:
        // domain: '.yourdomain.com'
    },
});

3. Security Headers

// middleware.ts
export async function middleware(request: NextRequest) {
    const response = NextResponse.next();
    const result = await csrfProtect(request, response);

    if (result.success) {
        // Add security headers
        result.response.headers.set('X-Content-Type-Options', 'nosniff');
        result.response.headers.set('X-Frame-Options', 'DENY');
        result.response.headers.set('X-XSS-Protection', '1; mode=block');
        result.response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
    }

    return result.response;
}

🔧 Advanced Usage

Multiple CSRF Strategies

// middleware.ts
import {createCsrfMiddleware} from '@csrf-armor/nextjs';

const apiCsrf = createCsrfMiddleware({
    strategy: 'signed-token',
    secret: process.env.CSRF_SECRET!,
    token: {expiry: 3600}
});

const webCsrf = createCsrfMiddleware({
    strategy: 'signed-double-submit',
    secret: process.env.CSRF_SECRET!,
});

export async function middleware(request: NextRequest) {
    const response = NextResponse.next();
    const {pathname} = request.nextUrl;

    let result;
    if (pathname.startsWith('/api/')) {
        result = await apiCsrf(request, response);
    } else {
        result = await webCsrf(request, response);
    }

    return result.success ? result.response :
        NextResponse.json({error: 'Forbidden'}, {status: 403});
}

🤝 Contributing

We welcome contributions! Areas where help is needed:

  • Additional framework integrations
  • Performance optimizations
  • Security enhancements
  • Documentation improvements
  • Test coverage expansion

📄 License

MIT © Muneeb Samuels

📦 Related Packages


Questions? Open an issue or start a discussion!