How to Configure CSP Nonces for Server Scripts

This article outlines how web development teams implement and configure Content Security Policy (CSP) nonces to safely execute dynamically injected, server-rendered inline scripts. It covers the end-to-end architecture, starting from generating cryptographically secure pseudorandom values per request, setting the appropriate HTTP response headers, passing the generated tokens through template contexts, and avoiding critical security pitfalls such as cache pollution.

1. Generating a Cryptographic Nonce Per Request

A CSP nonce (number used once) must be unpredictable and unique for every individual HTTP request. Development teams implement server middleware to generate this token before any HTML rendering takes place.

  • The value should be generated using a cryptographically secure pseudorandom number generator (CSPRNG), such as crypto.randomBytes() in Node.js, secrets.token_urlsafe() in Python, or SecureRandom in Java/Ruby.
  • The raw entropy should be at least 128 bits (16 bytes), typically encoded as a Base64 or hexadecimal string.
  • Static or reused nonces negate CSP protection entirely, rendering the application vulnerable to replay-based Cross-Site Scripting (XSS).

2. Attaching the Nonce to the HTTP Response Header

Once the nonce is created for an incoming request, the server must declare it within the Content-Security-Policy HTTP header.

The directive format is:

Content-Security-Policy: script-src 'nonce-SEG1cGxlTm9uY2VWYWx1ZQ==' 'strict-dynamic'; object-src 'none'; base-uri 'none';

Key considerations for the header definition:

  • Enclose the prefix and value within single quotes: 'nonce-<base64-value>'.
  • Combining the nonce with 'strict-dynamic' allows trusted scripts to load child dependencies automatically, reducing the need to whitelist third-party CDNs.
  • Browsers that support CSP Level 3 will ignore 'unsafe-inline' when a valid nonce is present, allowing teams to maintain backwards compatibility for older user agents by specifying both.

3. Forwarding the Nonce Through the Request Context

To inject the nonce into the markup, the server framework must expose it to the rendering pipeline. Modern web frameworks handle this by attaching the nonce to the request-scoped context:

  • Express/Node.js: Stored on res.locals.nonce.
  • Next.js / SvelteKit: Stored in request headers via middleware and accessed within root layouts or server components.
  • Django: Injected via custom middleware into request.csp_nonce.
  • Spring Boot: Managed via request attributes in a servlet filter.

This design guarantees that the template engine or server-side rendering (SSR) component tree has read access to the exact token included in that specific HTTP response header.

4. Injecting the Nonce into Dynamic Script Elements

During HTML generation, dynamic scripts—such as client-side hydration scripts, application bootstrapping data, or inline event listeners—must have the nonce attribute explicitly defined.

<!-- Server-rendered inline bootstrap data -->
<script nonce="<%= res.locals.nonce %>">
  window.__INITIAL_STATE__ = { user: "authenticated" };
</script>

<!-- Server-injected external dynamic bundle -->
<script nonce="<%= res.locals.nonce %>" src="/static/bundle.js" async></script>

When rendering in JSX/React environments, the attribute must be provided as a prop:

export default function RootLayout({ children, nonce }) {
  return (
    <html>
      <head>
        <script
          nonce={nonce}
          dangerouslySetInnerHTML={{ __html: `window.ENV = "production";` }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Modern browsers extract the nonce attribute during the parse phase, check it against the CSP header, and immediately remove the attribute from the DOM element to prevent malicious client scripts from reading the value.

5. Managing HTTP Caching and Edge Proxies

Deploying nonces in front of reverse proxies and Content Delivery Networks (CDNs) requires strict caching configurations.

  • Never cache full HTML responses that contain nonces. If an edge cache stores an HTML document containing a nonce, every subsequent user will receive the same nonce, breaking the one-time-use security model.
  • For pages cached at the edge, configure headers to bypass caching (Cache-Control: no-store, private) on HTML routes.
  • Alternatively, employ edge-side compute workers (e.g., Cloudflare Workers, Fastly Compute, or Lambda@Edge) to dynamically generate the nonce, inject it into both the outgoing header and HTML stream, and serve static page content from the underlying cache.