Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next.js 13: How to pass nonce as prop so that initial scripts receive it? #42330

Closed
tmb-github opened this issue Nov 2, 2022 · 8 comments
Closed

Comments

@tmb-github
Copy link

Describe the feature you'd like to request

In Next.js 12, we simply add a nonce attribute to <Head> and <NextScript> (from "next/document") in _document.js in order for nonces to be written to the initial scripts that Next.js creates. In Next.js 13, adding those elements from next/document has been eliminated in the new app paradigm, and Next seemingly does it in the background.

Please clarify where we pass a nonce as a prop so that Next 13 adds the nonce to the initial scripts.

Describe the solution you'd like

Clarification on the above matter.

Describe alternatives you've considered

I cannot see how it can be done provided the current design pattern.

@mlbiche
Copy link

mlbiche commented Mar 7, 2023

@tmb-github Have you figured out how to do it? We are struggling with CSP due to this missing nonce. 😕

@tmb-github
Copy link
Author

@mlbiche Yes, I send all of the security headers in next.config.js, in an async headers() function in the nextConfig object. See this article for the syntax. Next, in every page.js file in the app folder, I include const generatedNonce = process.env.generatedNonce; before the JSX return, and in the JSX I write a <Head nonce={generatedNonce}></Head> that returns the custom Head element that I use from page to page, whose tag values change page-by-page according to what's needed in it. Please let me know if you need more clarification.

@mlbiche
Copy link

mlbiche commented Mar 7, 2023

@tmb-github Thanks for the explanations. But, I don't understand how you provide the per-page generated nonce in the CSP configured in the security headers in next.config.js as those headers seems to be prepared once when starting the app and not before each page request?

@tmb-github
Copy link
Author

@mlbiche Sorry about the oversight. Yes, the nonce is generated in the next.config.js and set as an environment variable so it can be retrieved with process.env.generatedNonce. The code I use to do this in next.config.js is:

const nonceGenerator = () => {
  const hash = crypto.createHash("sha256");
  hash.update(v4());
  return hash.digest("base64");
};

const generatedNonce = nonceGenerator();

The generatedNonce is then added to the nextConfig object like this, which makes it available as an environment variable:

const nextConfig = {
[...]
  env: {
    generatedNonce: `${generatedNonce}`,
  },
[...]
};

@hugues-m
Copy link

hugues-m commented Mar 8, 2023

Hi there,
@tmb-github thank you for your reply, I started implementation using your method, but as next.config.js is evaluated only once on server start, the nonce is the same for different page views.

" Nonces should be generated differently each time the page loads" https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce

After looking in next.js source code, the way to implement this it to pass a CSP header to the Request object from the middleware.

// middleware.ts

//...

  const nonce = generateContentSecurityPolicyScriptNonce()
  const cspNonceCondition = `'nonce-${nonce}'`
  
// ...

  const response = NextResponse.next({
    request: {
      headers: new Headers({
        'content-security-policy': securityPolicy,
        // Custom header for use in server component
        'x-mss-script-nonce': nonce,
      }),
    },
  })

// ...

  response.headers.append('Content-Security-Policy-Report-Only', securityPolicy)

// ...
  
return response

Then the nonce is different for each request and Next injects it automatically to its scripts ! (This seems weird and it is not documented but it works as intended by Next)

Then I added 'x-mss-script-nonce' header to get it back in server component (similar to your process.env, but with const nonce = headers().get('x-mss-script-nonce')

Hope this helps !

@hugues-m
Copy link

hugues-m commented Mar 8, 2023

Note that this is not compatible with static site generation as the nonce must be injected for each page view.

@tmb-github
Copy link
Author

tmb-github commented Mar 9, 2023

@hugues-m Thanks for that...although I'm having trouble understanding how to revise my existing next.config.js file to incorporate that design pattern. It currently follows the syntax here: https://nextjs.org/docs/api-reference/next.config.js/headers How would I revise that to incorporate the middleware, or would I use it outside next.config.js, and if so, where? (Also, I'm not using TypeScript, so if the code could exclude that, it'd be helpful for me to read.)

@danieltott
Copy link
Contributor

danieltott commented May 10, 2023

@hugues-m is correct that you need to set the request header in NextReponse.next, but note that you also need to set it as a response header so that it is outputted to the browser!

Here's my middleware function (full source):

export async function middleware(request: NextRequest) {
  // generate CSP and nonce
  const { csp, nonce } = generateCsp();

  // Clone the request headers
  const requestHeaders = new Headers(request.headers);

  // set nonce request header to read in pages if needed
  requestHeaders.set('x-nonce', nonce);

  // set CSP header
  // switching for report-only or regular for repro on
  // https://github.com/vercel/next.js/issues/48966
  const headerKey =
    request.nextUrl.pathname === '/csp-report-only'
      ? 'content-security-policy-report-only'
      : 'content-security-policy';

  // Set the CSP header so that `app-render` can read it and generate tags with the nonce
  requestHeaders.set(headerKey, csp);

  // create new response
  const response = NextResponse.next({
    request: {
      // New request headers
      headers: requestHeaders,
    },
  });

  // Also set the CSP so that it is outputted to the browser
  response.headers.set(headerKey, csp);

  return response;
}

You can see this working here: https://nextjs-csp-report-only.vercel.app/csp

Repo: https://github.com/Sprokets/nextjs-csp-report-only

Note - you'll need to be on at least Next.js 13.4.0 for this to work (see comment)

@vercel vercel locked and limited conversation to collaborators May 11, 2023
@timneutkens timneutkens converted this issue into discussion #49648 May 11, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants