Using nonces with Next.js #54907
Replies: 33 comments 47 replies
-
Known open issues: #49830 |
Beta Was this translation helpful? Give feedback.
-
Hi @leerob! I decided to add a CSP to my playground project just this week so I appreciate the timely update to the docs! The policy blocked multiple components and reported quite a few errors to the browser console which I tried to find a solution for with nonces. Unfortunately, I haven't had much success. I'm wondering if I've misunderstood something in my implementation? It's all contained within the additional 3 commits on this branch if you want to take a look. A couple extra bits I noticed while working on it that might be helpful feedback:
Also, #45184 is another relevant issue I believe which could potentially be closed by this? Thanks for working on this! |
Beta Was this translation helpful? Give feedback.
-
Hi! Two questions in the topic on using CSP with nonce before I submit any new issues
I've added middleware as in docs, following layout code works as expected (nonce's are added):
However when I remove the displaying of nonce (hence - nonce is not "consumed" in component) nonce value is no longer added to script tag. Why is that?
|
Beta Was this translation helpful? Give feedback.
-
@leerob Appreciate the new documentation page, however this only addresses server components (not client components). Many of the "Related" issues in the big list above are about problems with getting CSP nonces to work in client components, but you seem to have blanket replied and closed all of them with the link to the docs that only addresses the server component solutions. Hoping those issues can be re-opened... as it stands now it seems there is no way to have an effective CSP (that avoids using |
Beta Was this translation helpful? Give feedback.
-
Thanks for the updates and new documentation! The support for nonces looks like it will greatly improve my ability to implement a strict CSP. However, I've upgraded to 13.5 (and tried on canary) and I can't seem to get the nonce value to be added to any script tags. This even happens with a clean install of the |
Beta Was this translation helpful? Give feedback.
-
I tried the latest version 13.5 with the example |
Beta Was this translation helpful? Give feedback.
-
@leerob Hi Lee - in theory I should be able to use "create-next-app" and scaffold a brand new app, then apply your middleware example for a CSP, and everything should "just work". However if I do exactly that I get the errors that @tszhong0411 shows above. Your example seems to work for adding third-party scripts but doesn't seem to work for basic Nextjs. I am sure I am missing something important. Would love to see a working example build off "create-next-app". |
Beta Was this translation helpful? Give feedback.
-
Hi, Since the nonce doesn't apply to a static page, what is the best approach in this case? Is there an alternative to using 'unsafe-inline'? Hash is a possibility for scripts I know beforehand, but next injects a bunch of scripts that are blocked unless we use 'unsafe-inline'. There is a better approach, because by using 'unsafe-inline' we are losing much of the security that CSP is supposed to provide. Thanks |
Beta Was this translation helpful? Give feedback.
-
Been fighting with CSP for a week now. Also on Nextjs "13.5.3". The issue I encountered is Agree with the others that a solution for static pages is highly appreciated! |
Beta Was this translation helpful? Give feedback.
-
@leerob, hi lee, is there any plan for next.js to implement hash based strict csp? nonce does not address the problem for static pages and it would be great if nextjs has a plan for this. |
Beta Was this translation helpful? Give feedback.
-
QQ: is there a way in Next.js (App Router) to allow CSP's One of the libraries I use in my Page component's code uses |
Beta Was this translation helpful? Give feedback.
-
For people looking to have a strict CSP on a static site, we've been using a postbuild script to rewrite the generated inline scripts into a separate chunk - #54152 (comment) |
Beta Was this translation helpful? Give feedback.
-
Hi @leerob, thank you for your nice documentation and examples. The error is away on production build When calling the at build time by the example generated error page by using a path, which is not specified, e.g. Could you please give some advice on updating the example, such that it runs with all its functions or is this not possible due to the statically generated sites? If so, is there a possibility to create the error pages as dynamic pages in the minimum example? |
Beta Was this translation helpful? Give feedback.
-
@leerob how about a case with NextJs app router and custom express server? I'm having a problem with CSP and I am not sure how I should tackle this. As soon as I add helmet to the express server it starts to block all scripts |
Beta Was this translation helpful? Give feedback.
-
From the documentation, I could not find how to get the nonce in I found out I can get the nonce from access const MyDocument = ({ props }: { props: { nonce: string } }) => {
const { nonce } = props
return (
<Html>
<Head nonce={nonce}>
<script
nonce={nonce}
async
src={`https://www.googletagmanager.com/gtm.js?id=${GTM_ID}`}
id="gtag-base"
/>
</Head>
<body>
<Main />
<NextScript nonce={nonce} />
</body>
</Html>
)
}
MyDocument.getInitialProps = async (ctx: DocumentContext) => {
const nonce = ctx.req ? ctx.req.headers["x-nonce"] : undefined
// This way only works for Windows. Apple and Mobile Devices does not work with this.
// const nonce = ctx.res?.getHeader("x-nonce")
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps, props: { nonce } }
} |
Beta Was this translation helpful? Give feedback.
-
Has anyone found a solution to @hendrikpeilke's issue? Because I'm facing it as well. |
Beta Was this translation helpful? Give feedback.
-
I am facing an issue which I saw mentioned a few times, however all the issues were closed pointing to this discussion. "dependencies": {
"next": "^14.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}, import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${process.env.NODE_ENV === "production" ? "" : `'unsafe-eval'`};
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
img-src 'self' https://avatars.githubusercontent.com blob: data:;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
block-all-mixed-content;
upgrade-insecure-requests;
`;
// Replace newline characters and spaces
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, " ")
.trim();
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set(
"Content-Security-Policy",
contentSecurityPolicyHeaderValue,
);
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
response.headers.set(
"Content-Security-Policy",
contentSecurityPolicyHeaderValue
);
return response;
};
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
{
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
}; |
Beta Was this translation helpful? Give feedback.
-
I really feel like nonces are a stopggap measure. By definition, nonces must be dynamic and so this change means that it's not possible to deploy a static export with CSP protection. |
Beta Was this translation helpful? Give feedback.
-
For people who want to use nonce in page router. I found the simplest way to solve it. // _app.tsx
// If getInitialProps is used in a custom _app.js, and the page being navigated to is using getServerSideProps, then getInitialProps will also run on the server.
// you don't have to get nonce in App, just return initialProps.
MyApp.getInitialProps = async (
ctx: NextAppContext
): Promise<AppInitialProps> => {
const initialProps = await NextApp.getInitialProps(ctx);
return initialProps;
}; read nonce // _document.tsx
interface ExtendedDocumentProps extends DocumentInitialProps {
nonce?: string;
}
const Document = (props: ExtendedDocumentProps) => {
const nonce = props.nonce;
render (
<Html lang="en">
<Head>
<script nonce={nonce} .../>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
Document.getInitialProps = async (
ctx: DocumentContext
): Promise<DocumentInitialProps & { nonce: string }> => {
const initialProps = await NextDocument.getInitialProps(ctx);
const nonce = ctx.req?.headers?.['x-nonce'] as string | undefined;
return {
...initialProps,
nonce,
};
}; it works on my project. |
Beta Was this translation helpful? Give feedback.
-
My middleware seems fine, except the new third party GoogleTagManager. However by using GoogleTagManager, like this:
I get this error: |
Beta Was this translation helpful? Give feedback.
-
I have followed this https://nextjs.org/docs/pages/building-your-application/configuring/content-security-policy documentation by next js and i am getting this error here is my middleware file
here is my _app.js file
|
Beta Was this translation helpful? Give feedback.
-
@leerob are there any plans to introduce support for hash based CSPs to nextjs? Or does it exist already and I've missed it? Having to switch to 'force-dynamic' feels pretty disappointing when we mainly chose Next for its SSG / caching capabilities. |
Beta Was this translation helpful? Give feedback.
-
Hi, any guidance on how to use nonces in non-dynamic pages with the app router? Thks in advance |
Beta Was this translation helpful? Give feedback.
-
Currently needing support for hashes for SSG! |
Beta Was this translation helpful? Give feedback.
-
@dan-goswag @carlosagsmendes @blakerutledge For anyone using static exports / SSG and struggling with CSP issues, here's a Node buildscript that generates SHA256 hashes for each script that isn't allowed by /**
* NextJS v14.1.3 doesn't support CSP hashes for Static Site Generation, so we have
* to do it ourselves by modifying the outputted index.html at build-time and
* feeding our hashes into the script-src CSP header directive in the infrastructure code.
*/
const { parse } = require("node-html-parser")
const { readFileSync, writeFileSync } = require("fs")
const { createHash } = require("crypto")
const WEB_RESOURCES_PATH = "../web/out"
const INDEX_PAGE_PATH = `${WEB_RESOURCES_PATH}/index.html`
const CSP_HASHES_OUTFILE = "csp-hashes.json"
const generateSha256CspHash = (value) => {
return `sha256-${createHash("sha256").update(value).digest("base64")}`
}
console.log(`Searching ${INDEX_PAGE_PATH} for inline <script> tags`)
const cspHashes = []
const root = parse(readFileSync(INDEX_PAGE_PATH).toString())
const inlineScripts = root.querySelectorAll("body script")
console.log(`Found ${inlineScripts.length} inline <script> tags`)
// <link> scripts in the <head> preloading JavaScript resources are also impacted by
// the script-src CSP directive
const preloadedScripts = root.querySelectorAll("head link[rel='preload'][as='script']")
console.log(`Found ${preloadedScripts.length} <link> tags preloading JavaScript files in <head>`)
console.log("Creating SHA-256 hashes for each script")
for (const inlineScript of inlineScripts) {
const innerTextScriptHash = generateSha256CspHash(inlineScript.text)
// Inline scripts with a src attribute need to use the exact same hashes used by the link tag
// that preloads the linked resource. If there is no associated <link rel='preload' as='script' ...>
// tag, then adding this extra hash should not cause problems
if (inlineScript.getAttribute("src") !== undefined) {
const linkedScript = readFileSync(`${WEB_RESOURCES_PATH}${inlineScript.getAttribute("src")}`, "utf8")
const linkedScriptHash = generateSha256CspHash(linkedScript)
inlineScript.setAttribute("integrity", `${innerTextScriptHash} ${linkedScriptHash}`)
cspHashes.push(`'${linkedScriptHash}'`)
} else {
inlineScript.setAttribute("integrity", innerTextScriptHash)
}
cspHashes.push(`'${innerTextScriptHash}'`)
}
for (const preloadedScript of preloadedScripts) {
// Firefox and Chrome expect the hash of the linked script, Safari expects the hash of the inner text
const innerTextScriptHash = generateSha256CspHash(preloadedScript.text)
const linkedScript = readFileSync(`${WEB_RESOURCES_PATH}${preloadedScript.getAttribute("href")}`, "utf8")
const linkedScriptHash = generateSha256CspHash(linkedScript)
preloadedScript.setAttribute("integrity", `${innerTextScriptHash} ${linkedScriptHash}`)
cspHashes.push(`'${innerTextScriptHash}'`)
cspHashes.push(`'${linkedScriptHash}'`)
}
// There may be duplicate hashes if the same resources are loaded two or more times, like
// when a <link> tag preloads a JS file and an inline script later consumes it.
const uniqueCspHashes = [...(new Set(cspHashes))]
console.log(`Persisting the following hash information to ${CSP_HASHES_OUTFILE}: ${uniqueCspHashes}`)
writeFileSync(CSP_HASHES_OUTFILE, JSON.stringify(uniqueCspHashes))
console.log(`Updating ${INDEX_PAGE_PATH} with new hashed scripts`)
writeFileSync(INDEX_PAGE_PATH, root.toString()) This script was inspired by @badmintonkid's post here The only third-party library used is node-html-parser Usage instructions:
You can place this in your build pipeline and feed the output values into wherever you're defining your CSP. Note that if you're using a CDN, you may need to keep track of the existing/previously-used set of hashes and trigger an invalidation if the hashes ever change so that your HTML and CSP stays in sync. If you're using AWS CDK and CloudFront like me, these hash values can be placed into your CloudFront invalidation can apparently be done in CDK by specifying a value for the |
Beta Was this translation helpful? Give feedback.
-
I did exactly how it is shown here: So basically, all the script tags which are being generated on build do not have the nonce values and this is why the error is caused. Went through many issues and discussions but cannot find any resolutions. |
Beta Was this translation helpful? Give feedback.
-
When using nonces on a standard SSR site, how do you make sure scripts work on error pages like 404 and 500 which are statically generated? How do you make that switch? #64023 |
Beta Was this translation helpful? Give feedback.
-
Hello, I seen and use nonce already with the code generated from nextjs. But the problem that come after that if I use Google Tag Manager and Analytics with Next Third party component, it doesn't have any nonce tag causing it failed to be called.... Is there any config for 3rd party next component config that could fix that? Thank you |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Is it possible to use nonce in Pages router (Next 13)? |
Beta Was this translation helpful? Give feedback.
-
After digging through many different issues and discussions, I've made a new page in the documentation (PR) specifically for Content Security Policy and nonces. This docs page:
nonce
with Middlewarenonce
in a route withheaders()
unsafe
nonce
Middleware from running on prefetches / static assetsFurther, we've patched some bugs and made improvements to
nonce
handling in Next.js that will be available in the latestcanary
version (for those of you time traveling from the future, upgrade to Next.js 13.5). We also updated thewith-strict-csp
example in theexamples/
folder, which is backlinked from the new documentation page.This discussion is for following up on the latest
nonce
updates and CSP documentation to continue the discussion, and combining many separate discussions into here.Related
nonce
isn't applied for Content-Security-Policy #21925Beta Was this translation helpful? Give feedback.
All reactions