Skip to main content Link Menu Expand (external link) Left Arrow Right Arrow Document Search Copy Copied

Securing the SPA Using HTTP Headers

An HTTP (HyperText Transfer Protocol) header is used to pass extra information with HTTP requests and responses. Each header is a key-value pair separated by a colon. When a user visits a website, the server receives a request from the browser. The server then responds to the browser and the response will include HTTP headers.

Headers give more context to a webpage and allow developers to protect their websites from various security attacks. Security headers provide the browser with more information regarding the resources that the website can access and how it behaves in relation to other websites that the user may be browsing simultaneously.

By adding security headers to the response of our pages, we can minimize the possibility of the following attacks:

  • Cross-Site Scripting (XXS): a web security vulnerability that allows an attacker to compromise the security of a web application by manipulating the website to force it to run harmful JavaScript code that enables the attacker to masquerade as the user and perform fraudulent activities on their behalf.
  • Clickjacking: an attack that tricks a user into clicking a webpage element which is invisible or disguised as another element. Clickjacking allows a hacker to force the user to download malware or provide credentials and sensitive information without their consent.
  • Code injection: an attack that involves injecting malicious code into an application from untrusted sources. Websites that connect to a multitude of domains without any restrictions are particularly susceptible to this type of attacks.

Next.js allows you to set security headers from the next.config.js file situated in the main folder of your project. We will make use of six different HTTP security headers to help protect our application.

X-Frame-Options header

The X-Frame-Options header is designed to prevent clickjacking attempts on a website by ensuring that the site’s content is not embedded in other websites.

We will set the value of the X-Frame-Options header to DENY in order to prevent browsers from loading our website in an <iframe><frame><object>, or <embed> element. Attackers can use these elements to add invisible controls on top of the UI elements of our website to trick users into giving out information or unwillingly performing harmful tasks.

Content-Security-Policy header

The Content-Security-Policy header allows you to set a policy for which domains are approved for executable scripts as well as the kind of resources (e.g., fonts, images, etc.) that can be loaded into your website.

We will use the Content-Security-Policy header to allow resources to be loaded only from the same origin (self) and enable inline scripts and styles, which are necessary for Material UI (MUI) components. We will also authorize our website to connect to the login.microsoftonline.com domain for SSO in addition to accessing Google Fonts to download the Roboto family of fonts.

X-Content-Type-Options header

The X-Content-Type-Options header is designed to disable Multipurpose Internet Mail Extensions (MIME) type sniffing, a technique used by browsers to determine type of a resource based on the response content instead of what is specified in the Content-Type header.

Attackers can manipulate the MIME sniffing algorithm to force the browser into interpreting data in a way that allows them to carry out malicious operations like cross-site scripting.

Using the the nosniff directive in the X-Content-Type-Options header forces the browser to adhere to the MIME types specified in Content-Type and thus reduces the possibility of XSS attacks.

Permissions-Policy header

The Permissions-Policy header allows you to specify the Web APIs are allowed to be used within your website. This security header enables you to opt out of using external devices that are not needed for your website to function. The geolocation, camera, microphone APIs and many more should always be disabled if they are not used in your web application. Disabling unused web APIs helps lower the risk of attackers exploiting these channels for fraudulent or malicious activities.

We will disable the camera, battery, geolocation and microphone web APIs since our demo website does not need them.

Referrer-Policy header

If your website contains clickable links to another domain then , then your website is said to be a referrer. Whenever a user clicks on one of those links, certain information about the referrer is sent to the target domain in the HTTP request’s referrer header.

The Referrer-Policy header allows developers to specify how much information about the referrer is sent with the referrer header in each HTTP request when navigating from one domain to another.

We will make use of the origin-when-cross-origin directive for the Referrer-Policy to send the path, origin, and query string when performing a same-origin request between equal protocol levels. An example of an equal protocol level would be from HTTPS to HTTPS.

Strict-Transport-Security header

The Strict-Transport-Security header instructs web browsers to connect to your website only via HTTPS , thus ensuring that every connection is encrypted and secure from infiltration by third parties. You can also specify the duration in seconds that the browser will remember this protocol, with the recommended duration being 31536000s or 1 year. We will also use the includeSubDomains directive to ensure that all subdomains also adhere tot eh HTTPS protocol requirement.

Adding HTTPS Security Headers to a Next.js SPA

To add HTTPS security headers to your Next.js SPA, simply open the  next.config.js file located in the root directory of your project and modify it as shown below:

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  env: {
    CLIENT_ID: process.env.CLIENT_ID,
    TENANT_ID: process.env.TENANT_ID,
    REDIRECT_URI: process.env.REDIRECT_URI,
  },
  webpack: (config) => {
    config.module.rules.push({
        test: /\.md$/,
        use: 'raw-loader',
    })

    return config
  },
  async rewrites() {
    return [
        {
            source: '/api/:path*',
            destination: `${process.env.HOST}/api/:path*`,
        }
    ];
  },
    // Adding policies:
    async headers() {
        return [
            {
                source: '/(.*)',
                headers: [
                    {
                        key: 'X-Frame-Options',
                        value: 'DENY',
                    },
                    {
                        key: 'Content-Security-Policy',
                        value: "default-src 'self' https://login.microsoftonline.com; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com  https://fonts.bunny.net; font-src 'self' https://fonts.gstatic.com  https://fonts.bunny.net data:; img-src 'self' data:;",
                    },
                    {
                        key: 'X-Content-Type-Options',
                        value: 'nosniff',
                    },
                    {
                        key: 'Permissions-Policy',
                        value: "camera=(), geolocation=(), microphone=()",
                    },
                    {
                        key: 'Referrer-Policy',
                        value: 'origin-when-cross-origin',
                    },
                    {
                        key: 'Strict-Transport-Security',
                        value: 'max-age=31536000; includeSubDomains',
                    }
                ],
            },
        ];
    },
}

module.exports = nextConfig