Skip to content

Implementing Nonces for Secure Inline Scripts

Feb 6, 2025 •
4 min read

I didn’t know what a nonce was at first. I discovered it when I was in the Tokopedia Web Security Group and started digging into web security, especially as we were on our way to implementing a robust Content Security Policy (CSP).

What is a Nonce?

A nonce (short for “number used once”) is a unique, random string generated for a single use, typically during a single request or session. In web security, nonces are commonly used in CSP headers. When a server sends a CSP header containing a nonce, any inline script on the page must include the same nonce value in its nonce attribute to execute. This mechanism helps prevent unauthorized or malicious scripts from running, thereby mitigating risks such as cross-site scripting (XSS) attacks.

Let me walk you through some of the common issues I encountered and how I fixed them.

Common Issue 1: Using a Static Nonce

At first, I thought using a hard-coded nonce would be enough. My code looked something like this:

// Example of using a static nonce (NOT FOR PRODUCTION)
const staticNonce = "12345"; // hard-coded nonce

const cspHeader = `Content-Security-Policy: script-src 'nonce-${staticNonce}'`;
const scriptTag = `<script nonce="${staticNonce}">console.log('Hello World');</script>`;

Why This Didn’t Work:

  • Predictability: A static nonce is easy for attackers to guess.
  • Reusability: Reusing the same nonce across requests can open the door for replay attacks.
  • Ineffective CSP: If someone figures out the nonce, the CSP won’t stop malicious scripts.

Common Issue 2: Nonce Mismatch Between Header and Inline Script

Another mistake I made was generating a nonce on the server but then accidentally using a different value in my inline script:

// Server generates a nonce for the CSP header
const serverNonce = generateSecureNonce(); // e.g., "a1b2c3"

// Setting the CSP header correctly
const cspHeader = `Content-Security-Policy: script-src 'nonce-${serverNonce}'`;

// But the inline script uses an incorrect nonce
const scriptTag = `<script nonce="wrongNonce">console.log('Secure script');</script>`;

Why This Was a Problem:

  • Nonce Mismatch: The browser will refuse to run the inline script if the nonce in the tag doesn’t match the nonce in the CSP header.
  • Broken Functionality: This can break important parts of your web app and lead to a poor user experience.

The Correct Approach: Generating a Unique Nonce Per Request

After some trial and error, I found the best solution: generate a new, secure nonce for every request and use it consistently. Here’s how I set it up:

// Utility function to generate a secure random nonce
function generateSecureNonce(length: number = 16): string {
  return require("crypto")
    .randomBytes(length)
    .toString("base64")
    .replace(/[/+=]/g, ""); // Clean up for safe usage in HTML attributes
}

// For every request, generate a unique nonce
const nonce = generateSecureNonce();

// Set the CSP header with the generated nonce
const cspHeader = `Content-Security-Policy: script-src 'nonce-${nonce}'`;

// Use the same nonce in your inline scripts
const inlineScript = `<script nonce="${nonce}">console.log('Secure inline script');</script>`;

Why This Works:

  • Unique Per Request: Each HTTP response gets its own unpredictable nonce.
  • Consistency: Using the same nonce in both the CSP header and inline scripts ensures everything runs smoothly.
  • Better Security: This method effectively stops attackers from injecting malicious scripts.

Use Cases for Nonces in Web Security

Nonces come in handy when you need to:

  • Allow inline scripts without compromising security.
  • Manage third-party scripts conditionally.
  • Render dynamic content safely in your web applications.

Conclusion

By generating a unique, secure nonce for every request and making sure it’s used consistently, I was able to reinforce our CSP and protect our inline scripts from potential XSS attacks.

Now, whenever I work on CSPs, I know exactly how to keep things secure without breaking functionality. Happy coding, and stay secure! 🚀

Connect

Related posts