CentralCSP

Main menu

All articles

Next Article

Get started with CSP

How to setup nonce with NextJS

Tuesday, December 3, 2024

5 min read

Theotime Quere

Theotime Quere

Content Security Policy is an important security feature that helps protect your web application from various attacks, particularly Cross-Site Scripting (XSS). When using Next.js with the App Router, implementing CSP with nonce-based script security requires some setup. Here's a complete guide on how to do it.

Before diving into the implementation, let's understand what CSP and nonce are and why they're important:

  • Content Security Policy is a security standard that helps prevent various types of attacks, especially XSS attacks, by controlling which resources can be loaded

  • A nonce is a unique, random number used once that helps validate trusted scripts and styles

  • Together, they create a robust security system where only scripts with valid nonces are allowed to execute

Important Note

This implementation is specifically for the Pages Router in Next.js. It's important to note that nonce values are only accessible server-side. Therefore, any components or features that require the nonce must be configured and rendered on the server side. Client-side components won't have access to the nonce value.

1. Create a Nonce Generator

First, let's create a utility function to generate nonces. Create a new file `utils/nonce.ts`. This function uses Node's crypto module to generate a cryptographically secure random value:

// utils/nonce.ts

// Important: This function must be used server-side only as crypto is a Node.js module
export function generateNonce() {
return Buffer.from(crypto.randomBytes(16)).toString('base64');
}

The function above creates a 16-byte random value and converts it to a base64 string, making it safe for use in HTML attributes and headers.

2. Configure Middleware

Create or modify your `middleware.ts` file in the root of your project to add CSP headers. The middleware runs before each request and sets up the security headers:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { generateNonce } from './utils/nonce';

export function middleware(request: NextRequest) {
// Generate unique nonce for each request
const nonce = generateNonce();

// Important: Store nonce in headers to access it throughout the request lifecycle
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-nonce', nonce);

// Important: Define CSP directives with nonce
// Each directive controls different resource types
const cspHeader = `
  default-src 'self';
  script-src 'self' 'nonce-${nonce}';  // Only allow scripts with matching nonce
  style-src 'self' 'nonce-${nonce}';   // Only allow styles with matching nonce
  img-src 'self';
  font-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim();

const response = NextResponse.next({
  request: {
    headers: requestHeaders,
  },
});

// Set the CSP header in the response
response.headers.set('Content-Security-Policy', cspHeader);

return response;
}

The middleware does several important things:

  • Generates a new nonce for each request

  • Sets up CSP directives that control what resources can be loaded

  • Passes the nonce through headers so it's available throughout the application

Server-Side Access Only

Remember that headers.get('x-nonce') only works in Server Components or server-side code. Client Components cannot access these headers directly.

3. Configure Custom Document

If you're using Pages Router or need more control over the document structure, modify your `pages/_document.tsx` file. This file is crucial for applying the nonce to initial HTML and scripts:

// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
// Important: Get nonce from request headers in getInitialProps
static async getInitialProps(ctx) {
  const initialProps = await Document.getInitialProps(ctx)
  const { req } = ctx
  // Note: This only works server-side
  const nonce = req ? req.headers['x-nonce'] : ''
  
  return { ...initialProps, nonce }
}

render() {
  const { nonce } = this.props as any

  return (
    <Html lang="en">
      {/* Important: Apply nonce to Head for all initial styles */}
      <Head nonce={nonce}>
        <meta charSet="UTF-8" />
        <link rel="icon" href="/favicon.ico" />
        
        {/* Example: Inline script must include nonce */}
        <script
          nonce={nonce}
          dangerouslySetInnerHTML={{
            __html: `
              // Your inline JavaScript here
              console.log('Initialized with nonce');
            `
          }}
        />
      </Head>
      <body>
        <Main />
        {/* Important: Apply nonce to NextScript for all Next.js scripts */}
        <NextScript nonce={nonce} />
      </body>
    </Html>
  )
}
}

4. Configure App Component

Modify your `pages/_app.tsx` to handle nonce with providers like Chakra UI, Emotion, or other third-party libraries. This setup ensures that all styles and scripts from these libraries are properly nonced:

// pages/_app.tsx
import { ChakraProvider } from "@chakra-ui/react";
import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import { ReCaptchaProvider } from "next-recaptcha-v3";

function App({ Component, pageProps, nonce }) {
// Important: Create Emotion cache with nonce for styled components
const cache = createCache({ 
  key: "css", 
  nonce: nonce 
});

return (
  <CacheProvider value={cache}>
    <ChakraProvider>
      {/* Important: Pass nonce to third-party providers that support it */}
      <ReCaptchaProvider
        nonce={nonce}
        reCaptchaKey="your-recaptcha-key"
      >
        <Component {...pageProps} />
      </ReCaptchaProvider>
    </ChakraProvider>
  </CacheProvider>
);
}

// Important: Get nonce from request headers in getInitialProps
App.getInitialProps = async ({ ctx }) => {
const { req } = ctx;
// Note: This only works server-side
const nonce = req ? req.headers["x-nonce"] : "";
return { nonce };
};

export default App;

5. Using with Next.js Script Component

When using Next.js's Script component, you can pass the nonce as a prop. This is particularly useful for loading external scripts safely:

import Script from 'next/script';
import { headers } from 'next/headers';

// Important: This must be a Server Component to access headers
export default function Page() {
// Note: headers() only works in Server Components
const nonce = headers().get('x-nonce');

return (
  <div>
    {/* Apply nonce to Script component for security */}
    <Script
      src="/your-script.js"
      nonce={nonce}
    />
  </div>
);
}

Important Notes

  • The nonce value must be unique for each request

  • CSP headers should be tested thoroughly in development

  • Consider using report-only mode initially: Content-Security-Policy-Report-Only

  • Some third-party scripts might require additional CSP directives

  • Remember to add any external domains you need to the appropriate CSP directives

Conclusion

This setup provides a strong security foundation for your Next.js application while maintaining functionality. Test thoroughly in your development environment before deploying to production.

Continue Reading

Stay safe, no more unsafe-inline

Learn more about unsafe-inline and how to properly setup your CSP to avoid using it.

2024-12-03

5 min read

Theotime Quere

Theotime Quere

Read more →

CSP enforce & report only

Understanding the difference between enforce and report only modes in Content-Security-Policy implementation.

2024-11-18

5 min read

Theotime Quere

Theotime Quere

Read more →

Docs

CSP ScannerCSP EvaluatorReporting Endpoint

Contact


CentralSaaS © 2025