Getting started

Install @next-safe/middleware from NPM

npm -i @next-safe/middleware
yarn add @next-safe/middleware
⚠️

Since version 0.9.0, support for Beta middleware has been dropped, so your project needs to use Next.js >= 12.2 with Stable root-level middleware. For migration to Stable middleware, have a look at the middleware upgrade guide

Quickstart: Strict Content-Security-Policy (CSP)

Create the file middleware.js in your Next.js project folder:

// middleware.js
import {
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 the file pages/_document.js in your Next.js project folder:

// pages/_document.js
import {
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 CSP
export const getServerSideProps = gsspWithNonce(async (ctx) => {
return { props: { message: "Hi, from getServerSideProps" } };
});
// the generated nonce also gets injected into page props
const Page = ({ message, nonce }) => <h1>{`${message}. Nonce ${nonce}`}</h1>;
export default Page;

Thats it. You should be all set now with a Strict CSP for your Next.js app!

💡

If you need additional guidance or details, have a look at the "Strict CSP configuration" guide.

Quickstart: CSP Violation Reporting

Add the reporting middleware in middleware.js:

// middleware.js
import {
chainMatch,
isPageRequest
csp,
reporting,
strictDynamic,
strictInlineStyles,
} from '@next-safe/middleware';
const securityMiddleware = [
csp(),
strictDynamic(),
reporting(),
];
export default chainMatch(isPageRequest)(...securityMiddleware);

Create the file pages/api/reporting.js to set up the reporting endpoint:

// pages/api/reporting.js
import { reporting } from "@next-safe/middleware/dist/api";
/** @type {import('@next-safe/middleware/dist/api').Reporter} */
const consoleLogReporter = (data) =>
console.log(JSON.stringify(data, undefined, 2));
export default reporting(consoleLogReporter);

Thats it. Browsers will send CSP violation reports to this endpoint. You can easily react on validated reporting data by adding any number of custom reporters.

💡

If you need additional guidance or details, have a look at the "CSP violation reporting" guide.

Send violation reports to Sentry

If you use Sentry for monitoring your app, there is a convenient helper sentryCspReporterForEndpoint to create a reporter, that ingests all CSP violations into your Sentry project:

// pages/api/reporting.js
import {
reporting,
sentryCspReporterForEndpoint,
} from "@next-safe/middleware/dist/api";
// lookup at https://docs.sentry.io/product/security-policy-reporting/
const sentryCspEndpoint = process.env.SENTRY_CSP_ENDPOINT;
const sentryCspReporter = sentryCspReporterForEndpoint(sentryCspEndpoint);
export default reporting(sentryCspReporter);

Quickstart: Compose middleware

// middleware.js
import {
chain,
chainMatch,
isPageRequest,
csp,
strictDynamic,
} from "@next-safe/middleware";
/** @type {import('@next-safe/middleware').ChainableMiddleware} */
const geoBlockMiddleware = (req) => {
const BLOCKED_COUNTRY = "GB";
const country = req.geo.country || "US";
if (country === BLOCKED_COUNTRY) {
const response = new Response("Blocked for legal reasons", { status: 451 });
// returning response terminates the chain
return response;
}
};
const securityMiddleware = [csp(), strictDynamic()];
/**
* geoBlockMiddleware will be invoked on all requests
* from `BLOCKED_COUNTRY` and then block the request
* and terminate chain by returning a response with status 451
*
* securityMiddleware will only run on requests
* that didn't get geo-blocked and only on requests for pages
*/
export default chain(
geoBlockMiddleware,
chainMatch(isPageRequest)(...securityMiddleware)
);
💡

If you need additional guidance or details, have a look at the "Compose middleware" guide.