Skip to content
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

Nextjs 15 strict-dynamic CSP doesn't add Nonce to Clerk Script #4455

Open
4 tasks done
lasseklovstad opened this issue Nov 1, 2024 · 6 comments
Open
4 tasks done
Labels
needs-triage A ticket that needs to be triaged by a team member

Comments

@lasseklovstad
Copy link

lasseklovstad commented Nov 1, 2024

Preliminary Checks

Reproduction

https://github.com/lasseklovstad/clerk-csp-issue

Publishable key

pk_test_bG9naWNhbC1nYXRvci0xMi5jbGVyay5hY2NvdW50cy5kZXYk

Description

I tried folowing this guide to include dynamic csp headers: https://clerk.com/docs/security/clerk-csp#implementing-a-strict-dynamic-csp

Steps to reproduce:

  1. Change name of .env.example -> .env and add secrets.
  2. Run npm install and npm run dev
  3. open localhost:3000 and see in console the csp error

I have hardcoded the clerk domain in the csp header for connect-src in middleware.ts but that is not relevant for this issue. I have also tried adding the nonce explicit in the layout.tsx:

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const nonce = (await headers()).get("x-nonce");
  return (
    <ClerkProvider nonce={nonce ?? ""}>

But it still doesn't work.

Expected behavior:

The nonce should be added to the clerk script, but doesn't seem to work on nextjs 15. I tested on a Nextjs 14 version and that seemed to work.

Actual behavior:

I get csp error in chrome:

Refused to load the script 'https://CLERK_DOMAIN.clerk.accounts.dev/npm/@clerk/clerk-js@5/dist/clerk.browser.js' because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-NjI5ZWM0MzQtYjllNi00NGU4LWJmMGEtNTUwN2M2ZDY3YTRm' 'strict-dynamic' https: http: 'unsafe-eval'". Note that 'strict-dynamic' is present, so host-based allowlisting is disabled. Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

Environment

System:
    OS: Windows 10 10.0.19045
    CPU: (12) x64 Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz
    Memory: 9.65 GB / 31.78 GB
  Binaries:
    Node: 20.12.2 - C:\Program Files\nodejs\node.EXE
    Yarn: 3.5.0 - C:\Program Files\nodejs\yarn.CMD
    npm: 10.5.0 - C:\Program Files\nodejs\npm.CMD
    pnpm: 9.4.0 - C:\Program Files\nodejs\pnpm.CMD
  Browsers:
    Edge: Chromium (128.0.2739.113)
    Internet Explorer: 11.0.19041.4355
  npmPackages:
    @clerk/nextjs: ^6.0.2 => 6.1.0
    @types/node: latest => 22.7.4
    @types/react: latest => 18.3.10
    @types/react-dom: latest => 18.3.0
    autoprefixer: latest => 10.4.20
    eslint: latest => 8.57.1
    eslint-config-next: 15.0.1 => 15.0.1
    next: 15.0.1 => 15.0.1
    postcss: latest => 8.4.47
    react: ^18.3.1 => 18.3.1
    react-dom: ^18.3.1 => 18.3.1
    tailwindcss: latest => 3.4.13
    typescript: latest => 5.6.2
@lasseklovstad lasseklovstad added the needs-triage A ticket that needs to be triaged by a team member label Nov 1, 2024
@richardasymmetric
Copy link

I had this exact same issue. Turns out it's expected and you need to add dynamic to <ClerkProvider>

@lasseklovstad
Copy link
Author

Thanks, but it is said in the docs that this is not recommended: https://clerk.com/docs/references/nextjs/rendering-modes#make-auth-data-available-globally-with-clerk-provider-dynamic-not-recommended

The dynamic prop does some more stuff that should not be needed when just wanting to pass down the nonce...?

@richardasymmetric
Copy link

richardasymmetric commented Nov 1, 2024

I think it has to do with clerk v6 moving from default dynamic rendering to default static rendering. If it's static rendered, then it can't get the nonce (because it's rendered at build time, not request time). Here's a PR that would have "fixed" the issue, but would also break static rendering

For my particular use case I'm fine with having <ClerkProvider dynamic> at the top level because my app is showing only user specific request data, however, if you wanted to still have static rendering in your app, you can move the ClerkProvider down the tree so that it only gates sections that you need to be dynamic. There are some bug reports/discussions about this around, but I can't find them right now, but my take away was move the provider down to where it's absolutely needed such as the sidebar/header sections that show the clerk component.

I think the documentation around this probably needs updating to provide better examples of having the provider around just the sidebar/header parts of the app

@BRKalow
Copy link
Member

BRKalow commented Nov 1, 2024

Hi all, yes the dynamic prop needs to be used in order for a nonce generated in your middleware to properly get passed down to Clerk. We'll get the docs updated!

Per the Next.js docs, your app must use dynamic rendering if a nonce is configured in this way (ref: https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy#adding-a-nonce-with-middleware)

@wujekbizon
Copy link

wujekbizon commented Nov 10, 2024

Hello everyone,

I'm experiencing the same issue, and it's quite problematic in my case. Adding dynamic hasn’t helped much for me. Instead of making the RootLayout component async, which, by the way, is not recommended, I moved the ClerkProvider to a separate asynchronous component.:

export default async function ClerkProviderWrapper({ children }: ClerkProviderWrapperProps) {
  const nonce = await headers().then((headers) => headers.get('x-nonce') ?? '')

  return (
    <ClerkProvider
      nonce={nonce}
      localization={plPL}
      appearance={{
        variables: {
          colorBackground: 'white',
          colorInputBackground: '#ffb1b1',
          colorText: '#09090a',
          colorShimmer: '#e8b8b1',
        },
      }}
    >
      {children}
    </ClerkProvider>
  )
}

I also followed the Clerk documentation to add the logic in my middleware.ts file, and that part works for me. However, the issue is that now all my pages have to be dynamic, which is not ideal. Out of the 19 pages in my app, 8 can be statically generated, and making everything dynamic could significantly increase my costs—especially since Vercel lambdas can be expensive.

Is there an actual fix for this, or am I missing something? If possible, could you update the documentation? It’s currently outdated, as it still shows headers() as a synchronous function. I really appreciate all your hard work, especially with the recent v6 update—thank you for that! I'm looking forward to a quick fix or additional guidance in the docs.

@BRKalow
Copy link
Member

BRKalow commented Nov 12, 2024

@wujekbizon A nonce is, by design, intended to only be used once. If you're using a CSP that contains a nonce generated in middleware, your application has to be dynamically rendered. This is a limitation of the framework, not something that is imposed by Clerk. Docs updates coming, thanks for calling that out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-triage A ticket that needs to be triaged by a team member
Projects
None yet
Development

No branches or pull requests

4 participants