Strict CSP configuration
Base setup
create a file middleware.js
in your Next.js project folder:
// middleware.jsimport { chainMatch, isPageRequest, csp, strictDynamic,} from "@next-safe/middleware";
const securityMiddleware = [ csp({ // your CSP base configuration with IntelliSense // single quotes for values like 'self' are automatic directives: { "img-src": ["self", "data:", "https://images.unsplash.com"], "font-src": ["self", "https://fonts.gstatic.com"], }, }), strictDynamic(),];
export default chainMatch(isPageRequest)(...securityMiddleware);
create a file pages/_document.js
in your Next.js project folder:
// pages/_document.jsimport { getCspInitialProps, provideComponents,} from "@next-safe/middleware/dist/document";import Document, { Html, Main } from "next/document";
export default class MyDocument extends Document { static async getInitialProps(ctx) { const initialProps = await getCspInitialProps({ ctx }); return initialProps; } render() { const { Head, NextScript } = provideComponents(this.props); return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html> ); }}
For every page under pages
that uses getServerSideProps
for data fetching:
import { gsspWithNonce } from "@next-safe/middleware/dist/document";
// wrap data fetching with gsspWithNonce // to generate a nonce for CSPexport const getServerSideProps = gsspWithNonce(async (ctx) => { return { props: { message: "Hi, from getServerSideProps" } };});
// the generated nonce also gets injected into page propsconst Page = ({ message, nonce }) => <h1>{`${message}. Nonce ${nonce}`}</h1>;
export default Page;
gsspWithNonce
is necessary with Next.js 12.2 as there seems no
longer a way to automatically tell apart routes with getStaticProps + revalidate
(ISR) from routes with getServerSideProps
. But that is needed to
make the right decision between Hash-based or Nonce-based Strict CSP.
Default CSP directives
Those are the minimal and sensible defaults this package provides as the common base for Strict CSPs:
const defaults = { directives: { "default-src": ["self"], "object-src": ["none"], "base-uri": ["none"], }, isDev: process.env.NODE_ENV === "development", reportOnly: !!process.env.CSP_REPORT_ONLY,};
CSP Converter
This is a litte tool to convert CSP header values to the object format expected by this lib. This can be used when working with tools that can record CSP header values from browsing your site.
One such tool is the Laboratory Extension for Firefox: https://addons.mozilla.org/en-US/firefox/addon/laboratory-by-mozilla/. It allows you to detect and record sources from browsing your site.
Add custom scripts
Just add them with next/script
and strategies afterInteractive
or lazyOnLoad
on the pages where you need them. If you want to include a script in all pages, add it to your pages/app.js
.
Custom scripts that must run before the page is interactive, have to be added to pages/_document.js
, with <Script strategy="beforeInteractive />
or with a regular <script>{inlineCodeString}</script>
tag as child of <Head>
.
If you stick to those recommendations, all your script usage will work automatically with the hybrid Strict CSP capabilites provided by this package.
The following files serve as examples for script usage:
NEVER add unsafe (inline) script code from dynamic data anywhere within <Head>
of pages/_document.js
/ next/head
or <Script>
of next/script
. Scripts in those places will be trustified for Strict CSP by this package during SSR.
How this behaves behind the scenes
<Script>
's with strategies afterInteractive
and lazyOnLoad
will become trusted by transitive trust propagation of strict-dynamic
and so will be all scripts that they load dynamically, etc. That should cover the majority of use cases.
<Script>
's with strategy beforeInteractive
you place in _document.js
and inline <script>
's you place as children of <Head>
in _document.js
are automatically picked up for Strict CSP by this package.
What this package will do with such scripts, depends:
getServerSideProps
(Nonce-based)
the script will eventually receive the nonce.
getStaticProps
(Hash-based)
- The script loads from
src
and has an integrity attribute: The integrity attribute/hash will be picked up for CSP. Don't forget to set{ crossOrigin: "anonymous" }
innext.config.js
, else the SRI validation will fail.
A nice tool to conveniently get the hash for such scripts: https://www.srihash.org/.
- The script loads from
src
and doesn't have an integrity attribute: The script will be replaced by an inline proxy script that loads the script. The hash of this proxy script will be picked up for CSP. The actual script eventually becomes trusted by transitive trust propagation ofstrict-dynamic
.
- The script is an inline script: The inline code of the script will be hashed, the hash will be set as integrity attribute of the inline script and the hash will be picked up by CSP.
Avoid unsafe inline styles (CSS-in-JS)
This package tries to provide a best effort solution to this, with a strictInlineStyles
middleware. The e2e test app of this package comes with a setup that uses both twin.macro + Stitches and Mantine without unsafe-inline
in style-src
.
For more information, visit a discussion on GitHub about problems and their solution for a setup with Mantine, a React component library that uses emotion CSS-in-JS under the hood.
The following files serve as the references for such setups:
This package might not always be able to solve this issue, as this is highly dependent on the actual CSS-in-JS framework and 3rd party libs (dynamically inject inline styles?) you use.
Set additional security headers
A good listing with explanations can be found in the Next.js docs
There are more security headers in addition to CSP. To set them conveniently, you can use the nextSafe
middleware that wraps the next-safe
package. Use it with CSP disabled and use the csp
middleware for your CSP configuration instead:
// middleware.jsimport { chainMatch, isPageRequest, csp, nextSafe, strictDynamic,} from "@next-safe/middleware";
const securityMiddleware = [ nextSafe({ disableCsp: true }), csp(), strictDynamic(),];
export default chainMatch(isPageRequest)(...securityMiddleware);
The configuration options of the nextSafe
middleware are the same as
documented at https://trezy.gitbook.io/next-safe/usage/configuration