Issue
I have an angular ssr application which hosted with express. I am using google analytics and now i would like to use helmet.js to harden my application.
Right now I faced that the google analytics inline script violates the SCP.
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-xxxxxxx');
</script>
Google says i should use nonce to identify my scripts and no i dont want to use unsafe-inline setting, because it is really bad practise. Anybody has an example code?
I tried plenty of codes from google and chat gpt.
Solution
At a high level, you'll do the following:
- Generate a nonce value, unique for each request. Save it to
res.locals.cspNonce
or equivalent. - Tell Helmet about this nonce.
- 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.
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.
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.
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.
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);
For more, see Helmet's docs.
Answered By - Evan Hahn
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.