Hardening Modern Web Apps: Implementing Content Security Policy (CSP) Headers and Strict CORS Rules in Next.js Middleware


Security is often treated as an afterthought in modern web development until a data breach or Cross-Site Scripting (XSS) exploit occurs. As web applications consume dozens of external analytics tools, payment widgets, and third-party scripts, the attack surface expands exponentially.
Without strict HTTP security headers, malicious scripts injected via compromised NPM packages or third-party dependencies can silently steal session tokens, read local storage data, and exfiltrate user inputs.
To defend web applications at the infrastructure level, developers must enforce a Strict Content Security Policy (CSP) and robust Cross-Origin Resource Sharing (CORS) rules using Next.js Edge Middleware.
Here is a technical blueprint for securing Next.js applications against modern XSS and data exfiltration threats.
Understanding Content Security Policy (CSP)
A Content Security Policy is an HTTP response header that instructs the browser on which dynamic resources are permitted to load and execute.
By default, an unconfigured browser will execute any inline <script> tag or external JavaScript file requested by the DOM. A strict CSP restricts resource execution by defining allowed origins for:
* script-src: Allowed JavaScript execution sources.
* style-src: Permitted CSS stylesheets and inline styles.
* img-src: Trusted image hosts and data URIs.
* connect-src: Permitted API endpoints for fetch and WebSocket connections.
* frame-ancestors: Controls whether your site can be embedded inside <iframe> tags (defending against Clickjacking).
Step-by-Step Implementation in Next.js Middleware
1. Dynamic Nonce Generation for Inline Scripts Modern frameworks like Next.js require inline hydration scripts to boot up. To allow these legitimate inline scripts while blocking unauthorized XSS injection, we generate a cryptographically secure random nonce (number used once) per request inside Edge Middleware.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// 1. Generate a random base64 nonce per request
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
// 2. Define the strict Content Security Policy
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https://laurincequijano.com;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
connect-src 'self' https://www.google-analytics.com;
upgrade-insecure-requests;
`.replace(/\s{2,}/g, " ").trim();
// 3. Clone request headers and inject nonce for Server Components
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", cspHeader);
// 4. Construct response with security headers
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
response.headers.set("Content-Security-Policy", cspHeader);
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-Content-Type-Options", "nosniff");
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
response.headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
return response;
}
export const config = {
matcher: [
{
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
};2. Restricting API Origins with CORS in Next.js Routes When building API routes that process user requests or third-party webhooks, you must enforce explicit CORS checks to prevent unauthorized web domains from querying your backend:
import { NextResponse } from "next/server";
const ALLOWED_ORIGINS = [
"https://laurincequijano.com",
"https://app.laurincequijano.com",
];
export async function POST(request: Request) {
const origin = request.headers.get("origin");
// Verify origin domain against allowed list
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
return new NextResponse(
JSON.stringify({ error: "CORS Policy: Origin forbidden" }),
{ status: 403, headers: { "Content-Type": "application/json" } }
);
}
const response = NextResponse.json({ success: true, message: "Request processed securely" });
if (origin && ALLOWED_ORIGINS.includes(origin)) {
response.headers.set("Access-Control-Allow-Origin", origin);
response.headers.set("Access-Control-Allow-Methods", "POST, OPTIONS");
response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
}
return response;
}Core Security Headers Breakdown
| Header Name | Recommended Value | Protection Goal |
|---|---|---|
Content-Security-Policy | default-src 'self'; script-src 'nonce-...' | Blocks unauthorized XSS & script exfiltration |
X-Frame-Options | DENY | Prevents Clickjacking attacks via <iframe> |
X-Content-Type-Options | nosniff | Blocks MIME-type sniffing exploits |
Referrer-Policy | strict-origin-when-cross-origin | Protects sensitive URL params in outbound links |
Permissions-Policy | camera=(), microphone=() | Disables hardware API access unless explicitly granted |
By layer-protecting your application with Edge Middleware nonces, CSP directives, and strict CORS filters, you ensure your web applications meet enterprise compliance standards while safeguarding user data against modern security threats.

Need premium developer consulting?
Let's discuss how we can build API split checkouts, dynamic Next.js interfaces, or speed audits for your business.
Get in Touch