Skip to content

Commit

Permalink
CSP nonce example
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanHahn committed Dec 4, 2023
1 parent bf116c5 commit 9298c5e
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 3 deletions.
5 changes: 2 additions & 3 deletions content/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,9 @@ app.use(

```js
// Sets the `script-src` directive to
// "'self' 'nonce-e33ccde670f149c1789b1e1e113b0916'"
// (or similar)
// "'self' 'nonce-e33...'" (or similar)
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
res.locals.cspNonce = crypto.randomBytes(32).toString("hex");
next();
});
app.use(
Expand Down
1 change: 1 addition & 0 deletions content/faq/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ title: "Frequently asked questions (FAQ)"
- [What if I don't want to install Helmet?]({{< ref "faq/you-might-not-need-helmet" >}})
- [How do I use Helmet without Express?]({{< ref "faq/use-without-express" >}})
- [How do I upgrade from Helmet 3 to Helmet 4?]({{< ref "faq/helmet-4-upgrade" >}})
- [How do I set a Content Security Policy nonce?]({{< ref "faq/csp-nonce-example" >}})
- [How do I set both `Content-Security-Policy` and `Content-Security-Policy-Report-Only` headers?](https://github.com/helmetjs/helmet/issues/351#issuecomment-1015498560)
- [Who made Helmet?]({{< ref "faq/contributors" >}})
127 changes: 127 additions & 0 deletions content/faq/csp-nonce-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
title: "Content Security Policy + nonce example"
---

Some `Content-Security-Policy` directives support a nonce value. It is one way to avoid using `unsafe-inline` with inline scripts and styles.

At a high level, you'll do the following:

1. Generate a nonce value, unique for each request. Save it to `res.locals.cspNonce` or equivalent.
1. Tell Helmet about this nonce.
1. Add the `nonce` HTML attribute to your relevant `<script>` or `<style>` tags.

## Step 1: generate a nonce value

First, you'll need to generate a nonce value and save it to `res.locals`. It should be difficult to guess this nonce, so we'll generate 32 random bytes (256 random bits) and convert them to a hex string.

```javascript
import * as crypto from "node:crypto";

// ...

app.use((_req, res, next) => {
// Asynchronously generate a unique nonce for each request.
crypto.randomBytes(32, (err, randomBytes) => {
if (err) {
// If there was a problem, bail.
next(err);
} else {
// Save the nonce, as a hex string, to `res.locals` for later.
res.locals.cspNonce = randomBytes.toString("hex");
next();
}
});
});
```

## Step 2: tell Helmet about this nonce

Next, tell Helmet about this nonce. More specifically, tell the `script-src` directive about it.

In this example, we plan to use this nonce with a `<script>` tag. If you want to use an inline style instead, use the `styleSrc` directive.

```javascript
app.use(
helmet({
contentSecurityPolicy: {
directives: {
scriptSrc: [
"'self'",
// Include this nonce in the `script-src` directive.
(_req, res) => `'nonce-${res.locals.cspNonce}'`,
],
},
},
}),
);
```

## Step 3: put the nonce in your HTML

Finally, you need to set the `nonce` attribute of your `<script>` or `<style>` tag.

It's likely that you're using a templating engine like Pug or EJS, but we'll do something simpler for this example and just send HTML inline.

```javascript
app.get("/", (_req, res) => {
// When rendering the `<script>` tag, include the nonce.
res.send(`
<script nonce="${res.locals.cspNonce}">
console.log("Hello world!");
</script>
`);
});
```

If you've done everything correctly, you should see "Hello world!" in the console. You should also see a difference `nonce` value every time you refresh the page.

## Full app code

Here's the full source code for this example.

```javascript
import express from "express";
import helmet from "helmet";
import * as crypto from "node:crypto";

const app = express();

app.use((_req, res, next) => {
// Asynchronously generate a unique nonce for each request.
crypto.randomBytes(32, (err, randomBytes) => {
if (err) {
// If there was a problem, bail.
next(err);
} else {
// Save the nonce, as a hex string, to `res.locals` for later.
res.locals.cspNonce = randomBytes.toString("hex");
next();
}
});
});

app.use(
helmet({
contentSecurityPolicy: {
directives: {
scriptSrc: [
"'self'",
// Include this nonce in the `script-src` directive.
(_req, res) => `'nonce-${res.locals.cspNonce}'`,
],
},
},
}),
);

app.get("/", (_req, res) => {
// When rendering the `<script>` tag, include the nonce.
res.send(`
<script nonce="${res.locals.cspNonce}">
console.log("Hello world!");
</script>
`);
});

app.listen(3000);
```

0 comments on commit 9298c5e

Please sign in to comment.