TYPeee

Understanding Content Security Policy (CSP) and Implementing it in Next.js

What is CSP?

Content Security Policy (CSP) is a security standard that helps prevent XSS, data injection and other types of attacks, by allowing developer to control which resources(e.g. scripts, stylesheets, images) can be loaded and executed on a web page. CSP works by defining a set of directives that specify the allowed sources for different types of content.

Features of CSP

1. Resource Whitelisting

CSP allows developer to specify the origins of resources can be loaded. For example, developer can specify that scripts can only be loaded from same origin or trusted domain

middleware.js
1Content-Security-Policy: script-src 'self' trustedDomain.com;

by specifying the trusted resources, developer can prevent malicious script from being executed on a web page, even if they are injected.

 

2. Nonce and Hash

CSP allows the use of nonces and hashes to specify inline script and stylesheets. This enables developers to include dynamic content in their pages while still maintaining security.

 

1) Nonce-based CSP

In nonce-based CSP, a nonce is dynamically generated for each request and included in the CSP header. then the nonce is included in 'nonce' attribute of script, styles tags and other things in HTML. This allow only the script and styles with matching nonces to execute.

middleware.js
1const nonce = Buffer.from(crpto.randomUUID()).toString();
2const directive = `script-src 'self' 'nonce-${nonce}'`
3
4const response = new Response(body, {
5	headers : {
6		'Content-Security-Policy': directive
7	}
8})
page.js
1<script nonce="nonce">
2	//script code
3</script>

 

2) Hash-based CSP

In hash-based CSP the CSP header includes hashes of allowed scripts and styles. The browser then calculated the hashes of inline scripts and styles in the page and compares them with the allowed hashes in the CSP header. If they match, the scripts and styles are allowed to execute.

middleware.js
1import crypto from 'crypto';
2
3//actual script content on the page
4const scriptContent = 'myFunction()';
5const scriptHash = crypto.createHash('sha256').update(scriptContent).digest('base64');
6
7const cspHeader = `script-src 'sha256-${scriptHash}'`
8
9const response = new Response(body, {
10	headers : {
11		'Content-Security-Policy': cspHeader
12	}
13})
page.js
1<script>
2	myFunction()
3</script>

 

3. Reporting

CSP provides mechanisms for reporting policy violations to a specific endpoint. This allow developer to monitor and debug policy violation in their application.

middleware.js
1const reportingGroup = {
2  group: "csp-endpoint",
3  max_age: 10886400,
4  endpoints: [
5    {
6      url: "/csp-report-endpoint",
7      priority: 1,
8    },
9  ],
10};
11
12const reportToHeader = JSON.stringify(reportingGroup);
13const cspOptions = `report-to csp-endpoint;`;
14
15const headerOptions = {
16  "Content-Security-Policy": cspOptions.trim(),
17  "Report-To": reportToHeader,
18};

 

4. Granuler Control

CSP directives are granular, allowing developers to fine-tune the security policy for their specific application needs. For example, developers can specify separate policies for different types of content or even individual pages.

middleware.js
1const cspOptions = "
2	default-src 'self';
3	script-src 'self';
4	style-src 'self';
5	img-src 'self';
6";
7
8const headerOptions = {
9	"Content-Security-Policy": cspOptions.trim()
10	
11};

 

Implementing in Next.js 14

middleware.js
1import { NextResponse } from "next/server";
2
3const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
4const cspOptions = `default-src 'self'; script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; style-src 'self' 'nonce-${nonce}'; img-src 'self' blob: data:; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none';`;
5
6const headerOptions = {"Content-Security-Policy": cspOptions.trim(),};
7
8export function middleware(request) {
9  const response = NextResponse.next();
10
11  Object.entries(headerOptions).forEach(([key, value]) => {
12    response.headers.set(key, value);
13  });
14
15  return response;
16}
17
18export const config = {
19  matcher: "/api/:path*",
20};
page.js
1import { headers } from "next/headers";
2import Script from "next/script";
3
4export default function Page() {
5	const nonce = headers().get('x-nonce');
6	const src= "https://www.example.com/script.js"
7	const strategy = "afterInteractive"
8	
9	return <Script src={src} strategy={strategy} nonce={nonce} />
10}

 

Reference

Next.js 14's Content Security Policy: Best Practices

Related Posts