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, orSecureRandomin 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.