Configure Clerk Content-Security-Policy headers
Content-Security-Policy (CSP) headers secure your document by preventing resources from being loaded from unexpected sources. This protects your apps from XSS attacks and data injections.
For Clerk to work correctly in your application, you'll need to configure the following CSP directives:
script-src
- This value should include the host application's FAPI hostname, such ashttps://clerk.your-domain.com
, as well as cloudflare's bot protection host,https://challenges.cloudflare.com
.connect-src
- This value should also include the host application's FAPI hostname, such ashttps://clerk.your-domain.com
.img-src
- This value should behttps://img.clerk.com
.worker-src
- Use the'self'
value to indicate that workers can be loaded from first-party scripts. Theblob:
schema value also needs to be included.style-src
- Due to Clerk's usage of runtime CSS-in-JS for styling,'unsafe-inline'
needs to be included.frame-src
- This value should also include cloudflare's bot protection host,https://challenges.cloudflare.com
.
The following example demonstrates a Next.js config file that sets the necessary directives for your application's assets and Clerk to load and function correctly. The values used apply to your currently selected instance, make sure to handle both your development instance and production instance hosts.
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://YOUR_FRONTEND_API_URL https://challenges.cloudflare.com;
connect-src 'self' https://YOUR_FRONTEND_API_URL;
img-src 'self' https://img.clerk.com;
worker-src 'self' blob:;
style-src 'self' 'unsafe-inline';
frame-src 'self' https://challenges.cloudflare.com;
form-action 'self';
`
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}
module.exports = nextConfig
Usage of unsafe-eval
and unsafe-inline
directives in Next.js
- Within
script-src
,unsafe-eval
is a requirement for Next.js to run in development mode. For production environment CSPs, it should be removed. - Within
script-src
,unsafe-inline
is a requirement for Next.js in both dev and prod environments if you're using the App Router and not usingstrict-dynamic
. If you are using the Pages Router, it can be removed. - Within
style-src
,unsafe-inline
is a requirement for Clerk's components to inject their styles. Removing this requirement is on our roadmap - please reach out to support if you'd like to see this implemented sooner.
Implementing a strict-dynamic
CSP
If you'd like to implement a strict-dynamic CSP, Clerk supports this, but with a different type of configuration. As strict-dynamic CSPs require a "nonce" value that should be programmatically generated per-request, the best way to make this work is within middleware that runs on every request. The following example demonstrates how to implement a strict-dynamic CSP with Next.js middleware, but the same approach could be used with any other framework.
import { NextResponse } from 'next/server'
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware((auth, request) => {
return applyCsp(request)
})
function applyCsp(request) {
// create a randomly generated nonce value
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
// format the CSP header
const cspHeader = `
default-src 'self';
script-src 'self' 'strict-dynamic' 'nonce-${nonce}' https: http: ${
process.env.NODE_ENV === 'production' ? '' : `'unsafe-eval'`
};
connect-src 'self' https://YOUR_FRONTEND_API_URL;
img-src 'self' https://img.clerk.com;
worker-src 'self' blob:;
style-src 'self' 'unsafe-inline';
frame-src 'self' https://challenges.cloudflare.com;
form-action 'self';
`
// Replace newline characters and spaces
const contentSecurityPolicyHeaderValue = cspHeader.replace(/\s{2,}/g, ' ').trim()
// set the nonce and csp values in the request headers
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: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
}
With this strict-dynamic
configuration in place, all script tags must be passed with a nonce
value or they will be blocked. This can be done by passing the nonce value as a nonce
parameter to the script tag. For example, <script src="https://example.com/script.js" nonce="<nonce_value>"></script>
. If you are using Next.js, any scripts loaded through next will automatically have the nonce value injected.
With the above middleware, the nonce value is made accessible via the x-nonce
request header. An example is provided below on how to access this value within a Next.js page.
import { headers } from 'next/headers'
export default function Page() {
const nonce = headers().get('x-nonce')
return <p>{nonce}</p>
}
If you're using one of Clerk's React-based SDKs, in order for Clerk to load correctly, the nonce value must also be passed to Clerk's <ClerkProvider>
component. This can be done by passing the nonce value as a nonce
prop to the <ClerkProvider>
component. For example:
import { ClerkProvider } from '@clerk/nextjs'
import { headers } from 'next/headers'
export default function Layout({ children }) {
return (
// Note: since this is server-rendered, you don't _need_ to pass the nonce, see note below
<ClerkProvider nonce={headers().get('x-nonce')}>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
)
}
If you are using Next.js and your layout file is rendered on the server (as is the case with the example above), Clerk's SDK will automatically read the nonce
from the request and pass it into <ClerkProvider>
. If you are rendering the provider on the client, you will need to explicitly pass the nonce
value to the client and into <ClerkProvider>
.
Feedback
Last updated on