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.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 the 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;
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.jsimport { 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.jsimport { 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.jsimport { 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.jsimport { 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.