How to setup nonce with NextJS

Tuesday, December 3, 2024
5 min read
Theotime QuereTheotime 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.

What is the CSP and Nonce?

Before diving into the implementation, let's understand what CSP and nonce are and why they're important:
  • Content Security Policy (CSP) is a security standard that helps prevent various types of attacks, especially XSS

  • A nonce is a cryptographically strong random value generated by the server for each HTTP request

  • Nonces must be unique for every page load and act as temporary security tokens

Let's see how nonces work in practice:

Example of how nonces work with CSP

// Server generates a nonce
const nonce = generateNonce(); // e.g., "abc123"

// CSP Header
Content-Security-Policy: script-src 'nonce-abc123';

// HTML with nonce
<script nonce="abc123">
  console.log("This script will execute");
</script>
<script>
  console.log("This script will be blocked");
</script>

SSR Limitation

This implementation is specifically for the Pages Router in Next.js. 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.

Static Generation Limitation

Since nonce values must be unique per request for security, pages using nonce cannot be statically generated. The page content will be different on each request due to the changing nonce value. You must use server-side rendering methods like getServerSideProps instead of static generation.

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

Nonce generator utility function

// 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 configuration for CSP and nonce

// 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);
requestHeaders.set('Content-Security-Policy', cspHeader);

// 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:
  • Creates a unique nonce for each request using cryptographically secure random values

  • Sets up security directives that control resource loading and incorporates nonce into script and style directives

  • Handles nonce distribution through headers to make it 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.

Next.js Nonce Handling

Next.js automatically extracts the nonce value from the 'Content-Security-Policy' request header and injects it into scripts that require it. This happens server-side during rendering - the nonce value is not accessible to client-side code. Next.js handles this process internally to ensure that all generated scripts are properly nonced.

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:

Custom Document configuration with nonce support

// 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:

App component configuration with nonce support

// 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:

Example of using nonce with Next.js Script component

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

  • Nonce must be unique per request, cryptographically secure, and have at least 128 bits of entropy

  • Test CSP headers thoroughly in development and consider using report-only mode initially

  • Some scripts may need additional CSP directives and external domains added to CSP

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.

Scan Your Website Now

Instantly analyze your website's Content Security Policy. Get actionable insights and improve your security posture in minutes.

Scan Your Website

Enter your website URL to analyze its Content Security Policy configuration.

Get started now by providing your website URL and launch the scan!

Your website is not yet online?
Try our CSP Evaluator