Next.js: consequence of AppRouter on your CSP

0xdbe · March 1, 2024

With the integration of AppRouter, Next.js undergoes significant internal changes in component loading and management. Underneath, AppRouter defaults to employing SSR (Server-Side Rendering) and leverages React Server Components (RSC). However, this transition brings about consequential impacts on CSP (Content Security Policy).

This article is written down as an ADR (Architectural Decision Record) because, from my point of view, this is an important security decision. This includes the context of how the decision was made and the consequences of adopting the decision. So, if you find it useful, you can share this ADR with your team members, and perhaps it will prove to be an effective strategy.

Issue

All Server Components are loaded using an inline script as depicted below:

<script>
  self.__next_f.push(
    [
        1,
        "1:HL[
            \"/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",
            \"font\",{\"crossOrigin\":\"\",
            \"type\":\"font/woff2\"}
           ]\n
         2:HL[
            \"/_next/static/css/0a4b1961022ab8f5.css\",
            \"style\"
           ]\n
         0:\"$L3\"\n
        "
    ]
    )
</script>

This script invokes self.__next_f.push() with the RSC payload, consisting of serialized HTML content. From a security standpoint, this presents an issue due to the presence of an unsafe inline script.

Considered Options

Disable AppRouter

With create-next-app, it’s always an option to create a Next.js app without AppRouter. When prompted, select No to the following question:

Would you like to use App Router? (recommended)  No / Yes

This choice replaces the AppRouter with the old PageRouter. However, it’s worth noting that this is not recommended by Next.js, as the PageRouter may potentially be deprecated in future releases.

Disable SSR

In order to disable Server Side Rendering, add use client in each component and layout. Unfortunately, this means forfeiting the benefits of SSR.

Use unsafe-inline

The simplest way to make AppRouter work is by adding unsafe-inline. However, this keyword disables protection against XSS (Cross-Site Scripting).

Use nonce

Another option to allow inline script is to use a nonce. A nonce is a unique, random string of characters created for one-time use.

This nonce must be inserted in the HTTP Header:

script-src: nonce-1234

And also in the HTML document:

<script nonce="1234">
  //...
</script>

Decision

Among the considered options, the only safe solution is to use a CSP with nonce in the script-src directive. While nonces should generally be avoided in a CSP, Next.js doesn’t currently provide any secure alternatives, and nonces are the only solution to maintain a strict CSP.

Consequence

In order to generate a random nonce for each request, a static storage solution like Bucket S3 can no longer be used to host a Next.js application. Thus, an application runtime is now required, potentially increasing hosting costs. Additionally, pages including a random nonce cannot be cached through a CDN (Content Delivery Network).

Twitter, Facebook