I've added a CSP to my Nextjs (v14.0.4) project. I can see that the nonce is being added when I run my project in dev mode. However when I build and start the project the nonces are not added to the next scripts. In the console the following error is shown:
Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' 'nonce-ODkxM2Y4NGEtN2Q3MS00N2E4LWIwOTktODc2YzFjMzFhNjg5' ". Either the 'unsafe-inline' keyword, a hash ('sha256-zlqnbDt84zf1iSefLU/ImC54isoprH/MRiVZGskwexk='), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.
This is my code. I call this function in the NextJs middleware:
function addCspHeaders(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const isDev = process.env.NODE_ENV !== 'production'
const nextCsp = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''};
style-src 'self' 'nonce-${nonce}';
img-src 'self';
font-src 'self' data:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
const cspHeader = nextCsp
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
}
How do I get my code working in production? If I add 'unsafe-inline' to script-src it does work in production, but I do not want to add 'unsafe-inline' to my CSP.
It turns out that the nonces documentation actually gives the answer:
Every time a page is viewed, a fresh nonce should be generated. This means that you must use dynamic rendering to add nonces.
I had to add the following line to my root layout.tsx
:
export const dynamic = "force-dynamic"
Also make sure the layout.tsx
does not have "use client"
.