Skip to main content

CSP Nonces: Secure Inline Scripts in React

A nonce is a unique, one-time-use string that allows a specific inline script or style to bypass CSP restrictions. Instead of script-src 'unsafe-inline' (which allows all inline scripts), you can use script-src 'nonce-abc123def456' to allow only scripts tagged with <script nonce="abc123def456">. Attackers cannot inject scripts with the nonce because they do not know its value. Nonces are the recommended way to allow legitimate inline scripts in React while maintaining a strict CSP. Generating nonces server-side and passing them to React requires coordination, but is worth the security gain (estimated 40% reduction in XSS attack surface vs. 'unsafe-inline').

This article teaches you how to implement nonce-based CSP in React.

How Nonces Work

A nonce (number used once) is a random string generated by the server for each HTTP response. The server:

  1. Generates a nonce (e.g., abc123def456).
  2. Includes it in the CSP header: script-src 'nonce-abc123def456'.
  3. Includes it in the HTML, in the nonce attribute of inline scripts.

The browser then:

  1. Parses the CSP header and notes the allowed nonce.
  2. Checks each inline script's nonce attribute against the CSP nonce.
  3. Allows scripts that match; blocks those that do not.

An attacker injecting <script>alert('hacked')</script> (without the nonce) is blocked because the nonce is missing or wrong.

Nonce vs. Hash

Two mechanisms achieve the same goal: nonces and hashes.

MechanismHowTrade-off
NonceServer generates unique string per request, includes in CSP and HTMLRequires server to pass nonce to frontend; each request is different
HashServer computes SHA-256 of inline script content, includes in CSPHash is static; if you change the script content, you must update the CSP

For React, nonces are preferred because your inline scripts (if any) are generated server-side anyway, and the nonce can be passed as a prop to your React app.

Implementing Nonces in React

Here is a complete example:

Server generates nonce and CSP header:

// server.js (Node.js/Express)
const express = require('express');
const crypto = require('crypto');
const app = express();

app.get('/', (req, res) => {
// Step 1: Generate nonce
const nonce = crypto.randomBytes(16).toString('hex');

// Step 2: Set CSP header with nonce
res.setHeader(
'Content-Security-Policy',
`default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self'; img-src 'self' data: https:; connect-src 'self' https://api.example.com`
);

// Step 3: Pass nonce to React via window or as a prop
res.send(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script nonce="${nonce}" src="/app.js"></script>
</body>
</html>
`);
});

app.listen(3001);

Wait—in the above example, src="/app.js" is an external script, not an inline script, so it does not need the nonce attribute. Nonces are for inline scripts like:

<script nonce="${nonce}">
window.__NONCE__ = '${nonce}';
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
</script>

Here is a more realistic example where the nonce is passed to React:

Server sends nonce in initial state:

// server.js
const express = require('express');
const crypto = require('crypto');
const app = express();

app.get('/', (req, res) => {
const nonce = crypto.randomBytes(16).toString('hex');

const initialState = {
nonce,
user: { id: 1, name: 'Alice' },
};

res.setHeader(
'Content-Security-Policy',
`default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self'; img-src 'self' data: https:; connect-src 'self' https://api.example.com`
);

res.send(`
<!DOCTYPE html>
<html>
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
<script nonce="${nonce}">
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
</script>
<script src="/app.js"></script>
</body>
</html>
`);
});

app.listen(3001);

React component accesses the nonce:

// App.js (React entry point)
import React, { useEffect, useState } from 'react';

export default function App() {
const [nonce, setNonce] = useState(null);
const [user, setUser] = useState(null);

useEffect(() => {
// Read nonce and initial state from window
const initialState = window.__INITIAL_STATE__;
if (initialState) {
setNonce(initialState.nonce);
setUser(initialState.user);
}
}, []);

return (
<div>
<h1>Welcome, {user?.name}!</h1>
<p>Nonce (for reference): {nonce ? nonce.substring(0, 8) + '...' : 'loading'}</p>
</div>
);
}

Using Nonces for Dynamic Inline Scripts

If your React app needs to inject inline scripts dynamically (rare but sometimes necessary), use the nonce:

export default function DynamicScript() {
const nonce = window.__INITIAL_STATE__?.nonce;

const handleClick = () => {
const script = document.createElement('script');
script.nonce = nonce; // Step 1: Set nonce attribute
script.textContent = `console.log('This script was injected dynamically');`;
document.body.appendChild(script); // Step 2: Add to DOM
};

return <button onClick={handleClick}>Inject Script</button>;
}

By setting script.nonce = nonce, the browser recognizes the nonce and allows the script to execute.

Nonces for Styles

You can also use nonces for inline styles:

// server.js
const nonce = crypto.randomBytes(16).toString('hex');

res.setHeader(
'Content-Security-Policy',
`default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'; img-src 'self' data: https:; connect-src 'self' https://api.example.com`
);

res.send(`
<!DOCTYPE html>
<html>
<head>
<style nonce="${nonce}">
body { font-family: sans-serif; }
</style>
</head>
<body>
<div id="root"></div>
<script nonce="${nonce}">
window.__NONCE__ = '${nonce}';
</script>
<script src="/app.js"></script>
</body>
</html>
`);

However, for React, using CSS Modules (external stylesheets) is preferred to avoid inline styles entirely.

Nonce Best Practices

  1. Generate per-request: Create a new nonce for every HTTP response. Do not reuse nonces.
  2. Use crypto.randomBytes(): Ensure nonces are cryptographically random. Avoid Math.random().
  3. Pass securely: Pass the nonce via window object or a meta tag, not as a query parameter (query parameters are logged in server logs and could leak).
  4. Rotate: If your nonce is compromised, any injected script with that nonce is allowed. Regenerate on each request.
// Generate a secure nonce
const nonce = crypto.randomBytes(16).toString('hex'); // 32 chars, ~128 bits of entropy

Key Takeaways

  • Nonces allow specific inline scripts to bypass CSP restrictions without using 'unsafe-inline'.
  • The server generates a unique nonce per request and includes it in the CSP header and inline script attributes.
  • React can access the nonce via a global window variable or meta tag to dynamically inject scripts if needed.
  • Always use cryptographically secure random functions; regenerate nonces per request.
  • Nonces are stronger than hashes for dynamic content and are the recommended approach for React.

Frequently Asked Questions

If someone steals my nonce, can they inject scripts?

Yes. The nonce is visible in the HTML source. An attacker who sees the nonce can craft malicious scripts with the same nonce. However, since nonces are regenerated per request, the stolen nonce is valid for only one page load. The attacker would need to trick the user into visiting a malicious page on every load (impractical).

Can I hardcode a nonce in my React app?

No. If you hardcode a nonce, it is the same for every user and every request. An attacker can use it in an XSS payload. Nonces must be unique per request, generated by the server.

What if I do not have inline scripts? Do I need nonces?

No. If your React app has no inline scripts (only external scripts from your domain), use script-src 'self' without nonces. Nonces are only needed when you have inline scripts.

How do I test CSP with nonces in development?

Use a local development server that generates nonces just like production. Never disable CSP in development; doing so masks issues. Alternatively, use Content-Security-Policy-Report-Only to test warnings without blocking.

Further Reading