Using nonces with Next.js #54907
Replies: 38 comments 52 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.
-
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.
-
So if I am using SSG with next.js v14.X.X, I need to give up on security with 'unsafe-inline'? |
Beta Was this translation helpful? Give feedback.
-
Any update for nonce in SSG? |
Beta Was this translation helpful? Give feedback.
-
I am experiencing issues setting up a strict Content Security Policy with the latest version of NextJS (currently 15.0.3).
Additionally, I've noticed that in development it's impossible to remove
It would be great to have some focus on the issue from the team, since CSP is quite an important aspect of security. |
Beta Was this translation helpful? Give feedback.
-
This is a huge problem for me as well. I’ve essentially been forced to make my entire app dynamic in order to keep it secure. This means I have to give up all of the following: ┌ ◐ / 11 kB 202 kB
├ ○ /_not-found 159 B 101 kB
├ ƒ /api/stripe/create-checkout-session 159 B 101 kB
├ ƒ /api/webhooks/clerk 159 B 101 kB
├ ƒ /api/webhooks/stripe 159 B 101 kB
├ ○ /blog 4.19 kB 123 kB
├ ○ /blog/[id] 188 B 110 kB
├ ○ /canceled 188 B 110 kB
├ ○ /polityka-prywatnosci 188 B 110 kB
├ ƒ /sign-in/[[...sign-in]] 723 B 130 kB
├ ƒ /sign-up/[[...sign-up]] 723 B 130 kB
├ ○ /success 6.26 kB 116 kB
├ ◐ /testy-opiekun 4.88 kB 154 kB
├ ○ /testy-opiekun/nauka 4.61 kB 114 kB
├ ○ /testy-opiekun/procedury 3.33 kB 122 kB
├ ○ /testy-opiekun/procedury/wyzwania 22.1 kB 128 kB
├ ○ /testy-opiekun/testy 4.36 kB 105 kB
├ ƒ /testy-opiekun/wyniki 5.02 kB 120 kB
├ ◐ /testy-opiekun/wyniki/[testId] 188 B 110 kB
├ └ /testy-opiekun/wyniki/[testId]
├ ○ /warunki 188 B 110 kB
└ ○ /wsparcie-projektu 736 B 116 kB
+ First Load JS shared by all 101 kB
├ chunks/3469eab5-8ce5f1d952c5d1ee.js 52.9 kB
├ chunks/4564-d6505d7cd6c5d89b.js 45.9 kB
└ other shared chunks (total) 1.92 kB Making everything dynamic could significantly increase my costs—especially since Vercel lambdas can be expensive. Beyond cost, this approach sacrifices the speed and performance benefits of static generation. Static pages are essential for delivering a fast, user-friendly experience, which is a priority for my app. Is the Next.js team planning any workarounds here? I’ve invested so much time forcing these new changes into my app, and now I have to rethink everything. |
Beta Was this translation helpful? Give feedback.
-
For v15.0.3. As i see it we have to enable csp only in production(npm run build && npm run start) and mark every page as async and have some async operation inside to avoid errors. If i am not mistaken, next/script doesn't require nonce, but what about next/image injecting width and height? Should those errors be ignored? I don't think it causes any problems though. To reproduce: import { headers } from "next/headers";
import Image from "next/image";
export default async function Home() {
const headersList = await headers();
const nonce = headersList.get('x-nonce') || "";
return (
<div>
<p className="red-box bg-green-500 size-20">
Hello World
</p>
<style nonce={nonce}>
{
`
.red-box {
background-color: red;
}
`
}
</style>
<Image
src={'./next.svg'}
alt="next logo"
width={100}
height={100}
/>
</div>
);
} import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
if (process.env.NODE_ENV === 'development') {
return NextResponse.next();
}
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
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: [
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
} |
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