Yagiz Navigate to the homepage
6 min read Security

Securing your Next.js 13 application

Next.js is a popular framework for building server-side rendered React applications, but like any web application, it’s crucial to take security seriously. In this blog post, we will discuss some essential security practices that you can implement in your Next.js application. We will cover topics such as security headers, including Content Security Policy, Referrer-Policy, and X-Frame-Options, as well as preventing cross-site request forgery attacks. By following these security best practices, you can help ensure the safety and privacy of your users’ sensitive information.

Security Headers

One of the essential security practices for Next.js applications is to implement recommended security headers. Security headers are HTTP response headers that allow web developers to control and enforce additional security mechanisms in their applications. The code snippet below shows an example of some recommended security headers to add to your Next.js application.

Adding security headers to your next.config.mjs file
const ContentSecurityPolicy = `
  default-src 'self' vercel.live;
  script-src 'self' 'unsafe-eval' 'unsafe-inline' cdn.vercel-insights.com vercel.live;
  style-src 'self' 'unsafe-inline';
  img-src * blob: data:;
  media-src 'none';
  connect-src *;
  font-src 'self';
`.replace(/\n/g, '');
 
const securityHeaders = [
  { key: 'Content-Security-Policy', value: ContentSecurityPolicy },
  { key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'X-DNS-Prefetch-Control', value: 'on' },
  { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
];
 
/** @type {import('next').NextConfig} */
export default {
  headers() {
    return [
      { source: '/(.*)', headers: securityHeaders },
    ];
  }
}

Content Security Policy

Content Security Policy (CSP) is a security header that allows you to specify a set of rules that define the sources from which the application can load resources such as scripts, styles, and images. It is designed to mitigate cross-site scripting (XSS) attacks by preventing the execution of scripts from untrusted sources. The value of the Content-Security-Policy header in the code snippet above specifies the allowed sources for various resources.

Referrer-Policy

Referrer-Policy is a security header that allows you to control how much information is passed in the HTTP Referer header when navigating from one page to another. The value of the Referrer-Policy header in the code snippet above specifies that the origin should be sent as the referrer when the request is made from the same origin, but not when the request is made from a different origin.

X-Frame-Options

X-Frame-Options is a security header that prevents clickjacking attacks by ensuring that a webpage can only be displayed in a frame or iframe if it is from the same origin. The value of the X-Frame-Options header in the code snippet above specifies that the page should not be displayed in any frame or iframe.

X-Content-Type-Options

X-Content-Type-Options is a security header that prevents MIME type sniffing, a vulnerability that allows attackers to trick the browser into interpreting a resource as a different MIME type, possibly executing malicious code. The value of the X-Content-Type-Options header in the code snippet above specifies that the browser should not try to guess the MIME type and should only use the declared MIME type.

X-DNS-Prefetch-Control

X-DNS-Prefetch-Control is a security header that controls whether the browser should prefetch DNS entries for links on a webpage. Prefetching DNS entries can reduce the latency of subsequent requests but can also be used for tracking purposes. The value of the X-DNS-Prefetch-Control header in the code snippet above specifies that DNS prefetching should be enabled.

Strict-Transport-Security

Strict-Transport-Security (HSTS) is a security header that enforces the use of HTTPS by the browser and prevents downgrade attacks. The value of the Strict-Transport-Security header in the code snippet above specifies that the browser should use HTTPS for all requests for the next 365 days, including subdomains, and preload the HSTS policy to all browsers.

Permissions-Policy

Permissions-Policy is a security header that allows you to specify which APIs and features are allowed to be used in the application. The value of the Permissions-Policy header in the code snippet above specifies that the camera, microphone, and geolocation APIs are not allowed to be used.

Cross Site Request Forgery

Cross-site request forgery (CSRF) is a type of attack that exploits the trust between a user and a website to perform unauthorized actions on the user’s behalf. CSRF attacks can lead to serious security breaches, such as account takeover, data theft, and malware installation. In this type of attack, an attacker tricks a user into executing a malicious action on a website without their knowledge or consent. Since the attack is carried out with the user’s credentials, it can be difficult to detect and mitigate.

Implement on your own

Here is a high level implementation

  1. Create a middleware.ts on your root folder.
  2. Filter middleware to grab only GET requests
  3. Create a secure cookie with a short expiration date with a value that is signed by the secret key only known by the server-side available environment key.
  4. Update the NextResponse headers with X-CSRF-Token header with the raw value
  5. On the server-side of the page you want to add CSRF protection, grab the http value using headers().get('X-CSRF-Token') and pass it to the client-side component.
  6. On the client-side of the page, create a hidden input with the value of the X-CSRF-Token header.
  7. On your route endpoint:
    1. Validate that the CSRF header exists
    2. Validate that a cookie is sent with the existing request
    3. Sign X-CSRF-Token value with your secret key and make sure that it is same as the cookie of the request.
    4. If any of these validations fail, throw an error.

Use a package

  1. Install edge-csrf module using npm install edge-csrf or yarn add edge-csrf.
  2. Create a middleware.ts on your root folder.
middleware.ts
import csrf from 'edge-csrf';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
 
// initalize protection function
const csrfProtect = csrf({
  cookie: {
    secure: process.env.NODE_ENV === 'production',
  },
});
 
export async function middleware(request: NextRequest) {
  const response = NextResponse.next();
 
  // csrf protection
  const csrfError = await csrfProtect(request, response);
 
  // check result
  if (csrfError) {
      return new NextResponse('invalid csrf token', { status: 403 });
  }
 
  return response;
}
  1. And on your React component:
pages/page.ts
import type { NextPage, GetServerSideProps } from 'next';
import React from 'react';
 
type Props = {
  csrfToken: string;
};
 
export const getServerSideProps: GetServerSideProps = async ({ res }) => {
  const csrfToken = res.getHeader('x-csrf-token') || 'missing';
  return { props: { csrfToken } };
}
 
const FormPage: NextPage<Props> = ({ csrfToken }) => {
  return (
    <form action="/api/form-handler" method="post">
      <input type="hidden" value={csrfToken}>
      <input type="text" name="my-input">
      <input type="submit">
    </form>
  );
}
 
export default FormPage;
  1. Create your API endpoint:
pages/api/form-handler.ts
import type { NextApiRequest, NextApiResponse } from 'next';
 
type Data = {
  status: string
};
 
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
  // this code won't execute unless CSRF token passes validation
  res.status(200).json({ status: 'success' });
}