Securing Your Next.js Application with Strict CSP
Tags: nextjs, security
Published: 2023-06-04 07:43:49

( Photo by <a href="https://unsplash.com/ko/@flyd2069?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">FLY:D</a> on <a href="https://unsplash.com/photos/ZNOxwCEj5mw?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a> )

Utilizing CSP (Content Security Policy) allows us to limit the execution of code on our website, thereby mitigating the scope of any potential damage caused by XSS (Cross-Site Scripting) attacks.

In this article, I'll share an example of how to handle CSP headers in Next.js.

## tl;dr;

- Set the policy according to Google's advocated Strict CSP
- Use _document.tsx to embed the CSP header into the `head` as `<meta httpEquiv="Content-Security-Policy" content={ ... Policy here ... } />`

## Prerequisites

- Next.js 12.x 
- Both SSR (Server Side Rendering) and CSR (Client Side Rendering) are used as rendering methods
- Loading external scripts using the [Script](https://nextjs.org/docs/pages/building-your-application/optimizing/scripts) component


## What is Strict CSP?

Google's proposed Strict CSP is a setting for CSP aimed at realizing higher security.

Strict CSP depends on the application use-case, hence, careful consideration is required to decide how to use it depending on your application's specific use-cases.

- [Introduction - Content Security Policy](https://csp.withgoogle.com/docs/index.html)

### What is the strict-dynamic directive?

Strict CSP uses the `strict-dynamic` directive to give trust to scripts. `strict-dynamic` decides whether to trust a script based on a nonce value or hash value generated from the script. Then, trust is automatically propagated to scripts that are dynamically loaded from that trusted script.

By using the `strict-dynamic` directive, the tedious task of managing whitelist becomes unnecessary, as you only need to grant trust to the top-level script.


![How trust is propagated by strict-dynamic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r6f9i44e1ylml0kvykwk.png)


## Implementing Strict CSP in Next.js

The implementation strategy was decided as follows:

- Primarily adhere to Strict CSP
- Trust scripts via nonce
  - This is due to the complexity of calculating the hash values of multiple external scripts
- Set the CSP header in the form of a meta tag
  - Although it is possible to return response headers via next.config.js, the values cannot be dynamically changed
  - I adopted the method of generating a meta tag in _document.tsx to dynamically generate nonce values

### Implementation Example

Below is an example of the implementation.

```tsx
import { randomBytes } from 'crypto'

import { Head, Html, Main, NextScript } from 'next/document'

const Document = () => {
  const nonce = randomBytes(128).toString('base64')
  const csp = `object-src 'none'; base-uri 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline' https: http: 'nonce-${nonce}' 'strict-dynamic'`

  return (
    <Html lang="ja">
      <Head nonce={nonce}>
        <meta httpEquiv="Content-Security-Policy" content={csp} />
      </Head>
      <body>
        <Main />
        <NextScript nonce={nonce} />
      </body>
    </Html>
  )
}

export default Document
```

- By using _document.tsx, you can embed the CSP header in the form of `<meta httpEquiv="Content-Security-Policy" content={ ... } />`.
    - Although you can set the response header in next.config.js, since the nonce needs to be generated for each page request, the CSP header is returned as a meta tag.
    - Specify the following directives in accordance with Strict CSP
        - `object-src 'none'`
        - `base-uri 'none'`
        - `script-src 'self' 'unsafe-eval' 'unsafe-inline' https: http: 'nonce-${nonce}' 'strict-dynamic'`
            - The value of `${nonce}` is a randomly generated dynamic value
- Pass the generated nonce value to the `nonce` property of the `Head`, `NextScript` components imported from `next/document`.

This is a brief introduction to handling CSP headers in Next.js.

## Let's check it out

Looking at the HTML generated by the running application, you'll find that the same nonce value set in the CSP header is set in each script tag.

![Generated HTML](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0o2v9havs6be4639rkjs.png)


By the way, if you forget to set the nonce in the `Head` component, you can confirm that the execution of the script stops and the following error message is displayed:

```
Refused to load the script '<URL>' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-eval' 'unsafe-inline' https: http: 'nonce-JyWbm9+... (snip) ...wURE=' 'strict-dynamic'". 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.
```

## In conclusion

That's all about how to handle CSP headers in Next.js.

When I was researching how to handle CSP headers in Next.js, I noticed there wasn't much compiled information on the topic, so I decided to compile it myself.

I hope this article is useful to someone.

## References

- https://kotamat.com/post/nextjs-strict-csp/
- https://csp.withgoogle.com/docs/strict-csp.html
- https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/Sources
- https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#strict-dynamic