nonce

Allowlists inline scripts and styles under Content Security Policy. A unique server-generated token per request that prevents unauthorized code injection.

Overview

The nonce attribute allows specific inline <script> and <style> elements to execute under a Content Security Policy (CSP) that otherwise blocks all inline code. The server generates a unique, unpredictable token for each HTTP response, places it in both the CSP header and the allowed elements, and the browser only executes inline code whose nonce matches.

Without nonces (or hashes), a strict CSP must either block all inline scripts — breaking many applications — or use 'unsafe-inline', which defeats the purpose of CSP entirely.

How It Works

  1. The server generates a cryptographically random nonce value for each request.
  2. The server includes the nonce in the Content-Security-Policy header: script-src 'nonce-VALUE'.
  3. The server adds nonce="VALUE" to every trusted inline <script> and <style> tag in the response.
  4. The browser compares each inline script/style nonce against the CSP header.
  5. Matching elements execute. Non-matching or nonce-less inline code is blocked.
  6. After parsing, the browser clears the nonce from the DOM so JavaScript cannot read it via element.getAttribute('nonce').
<!-- Server generates a unique nonce for each HTTP response --> <!-- CSP header: Content-Security-Policy: script-src 'nonce-abc123random' --> <!-- This inline script is allowed because its nonce matches the CSP header --> <script nonce="abc123random"> document.title = 'Page loaded securely'; </script> <!-- This inline style is allowed for the same reason --> <!-- CSP header: Content-Security-Policy: style-src 'nonce-abc123random' --> <style nonce="abc123random"> body { font-family: system-ui; } </style>

CSP Header Format

The nonce value in the CSP header must be prefixed with nonce- and wrapped in single quotes.

# CSP header allowing nonce-based inline scripts and styles Content-Security-Policy: script-src 'nonce-rAnd0mV4luE' 'strict-dynamic'; style-src 'nonce-rAnd0mV4luE' # Combined with other directives Content-Security-Policy: default-src 'self'; script-src 'nonce-rAnd0mV4luE' 'strict-dynamic'; style-src 'nonce-rAnd0mV4luE'; img-src 'self' https:

Server-Side Generation

The nonce must be generated server-side and must be unique per request. Use a cryptographically secure random number generator.

# Express.js — generate nonce per request import crypto from 'crypto'; app.use((req, res, next) => { const nonce = crypto.randomBytes(16).toString('base64'); res.locals.nonce = nonce; res.setHeader( 'Content-Security-Policy', `script-src 'nonce-${nonce}' 'strict-dynamic'; style-src 'nonce-${nonce}'` ); next(); }); # In your template: # <script nonce="<%= nonce %>">...</script>

strict-dynamic

The 'strict-dynamic' CSP directive works with nonces to create a trust chain. Scripts loaded dynamically by a nonced script are automatically trusted, even without their own nonce attribute. This simplifies CSP for applications that load third-party scripts at runtime.

<!-- With 'strict-dynamic' in the CSP, scripts loaded BY a nonced script --> <!-- are automatically trusted, even without their own nonce. --> <!-- CSP: script-src 'nonce-abc123' 'strict-dynamic' --> <script nonce="abc123"> // This dynamically added script is trusted because the parent has a nonce const s = document.createElement('script'); s.src = '/js/analytics.js'; document.head.appendChild(s); </script>

CSP via Meta Tag

CSP can be set via a <meta> tag instead of an HTTP header. However, the HTTP header is preferred because it cannot be bypassed by injected HTML before the meta tag.

<!-- CSP can also be set via a meta tag (but HTTP header is preferred) --> <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-abc123random'; style-src 'nonce-abc123random'" /> <script nonce="abc123random"> console.log('Allowed by meta CSP'); </script>

Security Considerations

  • Never hardcode nonces. A static nonce defeats the purpose — an attacker who discovers it can inject their own nonced scripts. Generate a new one per request.
  • Never cache pages with nonces. If a CDN or browser cache serves a stale page, the nonce in the HTML will not match the nonce in the fresh CSP header, blocking all inline code. Use Cache-Control: no-store on pages with nonces.
  • DOM clearing protects you. After page load, element.nonce returns the value via the IDL property, but element.getAttribute('nonce') returns an empty string. This prevents injected scripts from reading the nonce out of existing elements.
  • Nonces do not protect external scripts. The nonce attribute only affects inline code. Use 'strict-dynamic' or explicit URL allowlists for external script sources.

Limitations

  • Requires server-side rendering: Static sites served from a CDN cannot use nonces because there is no server to generate a unique value per request. Use CSP hashes instead.
  • Caching conflicts: Pages with nonces must not be cached, since the nonce changes on every request.
  • Not for event handlers: Nonces do not apply to inline event handler attributes like onclick. Move event handling to nonced script blocks.
  • Debugging difficulty: CSP violations are logged in the console, but the error messages do not always clearly indicate a nonce mismatch versus a missing nonce.

See Also

  • integrity — verify external script and stylesheet contents
  • crossorigin — CORS mode required alongside SRI
  • sandbox — iframe restrictions that complement CSP