Description
EDIT: See this comment below for an important update.
Verify canary release
- I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
Platform: win32
Arch: x64
Version: Windows 10 Home
Binaries:
Node: 18.17.0
npm: N/A
Yarn: N/A
pnpm: N/A
Relevant Packages:
next: 13.5.1-canary.1
eslint-config-next: N/A
react: 18.2.0
react-dom: 18.2.0
typescript: N/A
Next.js Config:
output: N/A
Which example does this report relate to?
with-strict-csp
What browser are you using? (if relevant)
Chrome 116.0.5845.188 (Official Build) (64-bit)
How are you deploying your application? (if relevant)
Vercel, Nodejs 18
Describe the Bug
According to the docs and example, the correct way to implement a strict CSP with nonces is by using middleware. However, even with a clean install of the example, the nonce is not being added to scripts correctly.
The nonce
property is appearing on <script>
tags, however the value is always empty.
<body>
<!-- ... -->
<script src="/_next/static/chunks/webpack.js?v=1695191539001" nonce="" async=""></script>
<script nonce="">(self.__next_f=self.__next_f||[]).push([0])</script>
<!-- etc. -->
</body>
A custom next/script
tag also does not have the nonce value added.
export default function RootLayout({ children }) {
const nonce = headers().get("x-nonce");
console.log(nonce);
return (
<html lang="en">
<body>{children}</body>
<Script
src={`https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX`}
strategy="afterInteractive"
nonce={nonce}
/>
</html>
);
}
gives us:
<script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX" nonce="" data-nscript="afterInteractive"></script>
If I try to show the nonce value on the page itself, it will display valid nonce value, so I don't think the error is with the middleware/nonce creation.
// app/page.js
import { headers } from "next/headers";
export default function Page() {
const nonce = headers().get("x-nonce");
console.log(nonce);
return (
<p>nonce: {nonce}</p>
);
}
gives us:
<p>nonce: <!-- -->MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx</p>
Our CSP response header is also correctly showing the nonce:
default-src 'self'; script-src 'self' 'nonce-MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx' 'strict-dynamic' 'unsafe-eval';
Expected Behavior
Next's inline <script>
tags (including ones created from <Script>
components) should have the nonce value added.
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
//nonce = MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx
//...
requestHeaders.set('x-nonce', nonce);
requestHeaders.set(
'Content-Security-Policy',
// Replace newline characters and spaces
cspHeader.replace(/\s{2,}/g, ' ').trim()
);
return NextResponse.next({
headers: requestHeaders,
request: {
headers: requestHeaders,
},
})
}
// app/layout.js
import { headers } from "next/headers";
import Script from "next/script";
export default function RootLayout({ children }) {
const nonce = headers().get("x-nonce");
console.log(nonce);
return (
<html lang="en">
<body>{children}</body>
<Script
src={`https://www.googletagmanager.com/gtag/js?id=G-AAAAAAAAAA`}
strategy="afterInteractive"
nonce={nonce}
/>
</html>
);
}
Should output:
<html lang="en">
<head><!-- ... --></head>
<body>
<!-- ... -->
<script src="/_next/static/chunks/webpack.js?v=1695191539001" nonce="MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx" async=""></script>
<!-- ... -->
<script src="https://www.googletagmanager.com/gtag/js?id=G-AAAAAAAAAA" nonce="MzgyYzQ2OGQtMWRhYS00OWFjLTk5NWUtYzliOTM4YjI4NmMx" data-nscript="afterInteractive"></script>
</body>
</html>
To Reproduce
- Install the example (with a command such as
npx create-next-app --example with-strict-csp with-strict-csp-app
) - Run the example (
npm run dev
) - Look at the HTML output in console and check
<script>
tags for the nonce value.