Security breaches in websites are often facilitated by poorly or inadequately configured vital security features. Any undetected security vulnerability leaves your site at risk of being hacked by malicious users. This could lead to disastrous consequences like data theft and unauthorized operations.
As a website administrator, you must therefore take the necessary steps to secure your website or application. Using security headers with the right directives is a highly effective way to secure websites from common security threats such as cross-site scripting (XSS) and clickjacking.
In this article, we’ll learn about security headers, their roles in website security, and how to add them to a Next.js application. We will cover:
Every time a browser communicates with a web server, several other pieces of information are sent along with the actual payload using HTTP headers.
When a browser requests a resource from a web server, it includes additional information about the request in the HTTP request headers. The server then responds with the requested content along with HTTP response headers. Take a look at the graphic below to see how this works:
While there are many different header fields for conveying different kinds of information in HTTP requests and responses, our focus is on security headers.
Security headers are a unique set of header fields used by web applications to specify directives that web browsers must adhere to or enforce. The purpose of these headers is to implement a set of protective rules to ensure secure communication between the user and the website.
For this demonstration, I have created a new Next.js app, which uses the default setup configurations. I have also started the Next app at http://localhost:3000 by running npm run dev
on my terminal.
Upon inspecting the HTTP response headers in the network tab of the developer console, it’s obvious that the starter Next app does not have the security headers enabled by default. The image below shows the active HTTP response headers:
As expected, there is neither Content-Security-Policy
, X-Content-Type-Options
, X-Frame-Options
, Referrer-Policy
, nor any other security header present in the response header. Without these policies, the app will be left vulnerable to several security threats, including cross-site scripting (XSS) and clickjacking.
Before learning how to add these security headers to a Next.js app, let’s first understand how each of these headers works. Feel free to skip to the last section of this article if you’re already familiar with security headers.
In the following sections, we will go over six different HTTP security headers that you should be aware of. We recommend implementing these security headers in your Next.js project if possible to strengthen app security.
X-Content-Type-Options
headerThe X-Content-Type-Options
header is designed to disable MIME type sniffing, a technique used by browsers to determine the Multipurpose Internet Mail Extensions (MIME) type of a resource based on the response content instead of what is specified in the Content-Type
header.
Though MIME sniffing is a helpful method for determining the content type, it’s also possible for an attacker to manipulate the MIME sniffing algorithm. By doing so, they can confuse the browser into interpreting data in a way that allows the attacker to carry out malicious operations like cross-site scripting.
To counter this security vulnerability, the X-Content-Type-Option
header supports the nosniff
directive, which forces the browser to adhere to the MIME types specified in Content-Type
:
X-Content-Type-Options: nosniff
Content-Security-Policy
(CSP) headerOftentimes, your app might need to execute scripts coming from origins other than its own. This will leave the app vulnerable to cross-site scripting (XSS) attacks, a client-side code injection attack characterized by the injection of malicious code by an attacker into a legitimate web page or web application.
With the use of the Content-Security-Policy
header, we can specify the exact domains we want to allow content from — in other words, which domains should be considered as safe, and which domains shouldn’t.
Below, you can see the general syntax:
Content-Security-Policy: default-src <trusted-domains>
<trusted-domains>
can be any of the following:
*
to allow content from all domains'self'
to appoint your own domain as the only trusted domain'https://logrocket.com'
The Content-Security-Policy
header also allows us to set custom security policies for various resources, including images and other media, fonts, styles, scripts, and more, all using their respective directives.
Take the following, for example:
Content-Security-Policy: default-src 'self' 'https://blog.logrocket.com'; image-src 'https://unsplash.com'; script-src 'self' https://www.google-analytics.com; font-src 'self' 'https://fonts.googleapis.com';
In the above policy, we specified that only content originating from our site and blog.logrocket.com
is allowed. These two domains will be used as sources for all our resources unless otherwise specified.
We also only allowed scripts from the current domain — defined as self
— as well as google-analytics.com
.
For fonts, we allowed both the current domain and fonts.googleapis.com
to be sources, while images are only trusted from unsplash.com
.
X-Frame-Options
headerThe X-Frame-Options
header is designed to thwart clickjacking attempts on a website by ensuring that the site’s content is not embedded in other websites.
By setting the DENY
directive for this header, we prohibited browsers from loading a page in an <iframe>
, <frame>
, <object>
, or <embed>
element, regardless of which site is loading the page:
X-Frame-Options: DENY
Another more lenient option is to only allow the page to be displayed in an iframe existing on the same origin as the page itself. This is achieved by setting the directive for X-Frame-Options
as SAMEORIGIN
, like so:
X-Frame-Options: SAMEORIGIN
Strict Transport Security
(HSTS) headerThe Strict-Transport-Security
header instructs web browsers to connect with web servers only via HTTPS, thus ensuring that every HTTP connection is encrypted and secure from infiltration by third parties.
The directives for this header are max-age
, SubDomains
, and preload
. Here’s an example of what this looks like:
Strict-Transport-Security: max-age=3571000; includeSubDomains; preload
max-age
is the only required directive; the rest are optional. max-age
specifies how long the browser should remember that a site is only to be accessed using HTTPS.
Permissions-Policy
headerThe Permissions-Policy
header, formerly known as Feature-Policy
, allows you to specify the Web APIs that the web browser is permitted to use.
This means that you can opt not to use external devices — such as the camera, microphone, and geolocation — if your site doesn’t need them. This helps to lower the risk of attackers exploiting such channels.
Here’s an example of what the header looks like:
Permissions-Policy: camera=(); battery=(self); geolocation=(); microphone=('https://a-domain.com')
The empty brackets for both camera
and geolocation
signify to the browser that we’re denying the use of both APIs. In addition, we specified that the battery status API should only be allowed for the current domain, while the microphone should only be allowed in the specified domain.
Referrer-Policy
headerWhen you click on a link to go from one domain to another domain — say, from DomainA
to DomainB
— then DomainA
is said to be the referrer in this case, and certain information about the referrer is sent to DomainB
in the HTTP request’s referrer header.
The Referrer-Policy
header allows you 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.
There are many directives that you can use with the Referrer-Policy
header. This example below uses origin-when-cross-origin
to send the path, origin, and query string when performing a same-origin request between equal protocol levels — for example, between HTTPS and HTTPS.
Referrer-Policy: origin-when-cross-origin
Take a look at the MDN docs to learn more about the other directives you can use with the Referrer-Policy
header.
Now that we know the different security headers and each of their roles in securing a website, let’s see how to add them to a Next.js application.
Next allows you to set security headers from the next.config.js
file situated in the main folder of your project — you might need to create this file if it is not already present. Here, you must add an asynchronous headers
function to the object.
The headers
function must return an array containing a single object. The object will contain the source to which you want to apply the headers, as well as the actual directives themselves.
const nextConfig = { reactStrictMode: true, swcMinify: true, // Adding policies: async headers() { return [ { source: '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY', }, { key: 'Content-Security-Policy', value: "default-src 'self' 'https://blog.logrocket.com'; image-src 'https://unsplash.com'; script-src 'self' https://www.google-analytics.com; font-src 'self' 'https://fonts.googleapis.com'", }, { key: 'X-Content-Type-Options', value: 'nosniff', }, { key: 'Permissions-Policy', value: "camera=(); battery=(self); geolocation=(); microphone=('https://a-domain.com')", }, { key: 'Referrer-Policy', value: 'origin-when-cross-origin', }, ], }, ]; }, } module.exports = nextConfig
The above example sets security headers for all routes on the site, as specified by the source /(.*)
. There might be instances where you just want a set of headers set for a given page. You could do that as well, as shown below:
const nextConfig = { reactStrictMode: true, swcMinify: true, // Adding policies: async headers() { return [ { source: '/user', headers: [ { key: 'Content-Security-Policy', value: "default-src 'self' 'https://blog.logrocket.com'; image-src 'https://unsplash.com'; script-src 'self' https://www.google-analytics.com; font-src 'self' 'https://fonts.googleapis.com'", } ] }, { source: '/posts', headers: [ { key: 'Content-Security-Policy', value: "default-src 'self'", } ] }, ]; }, } module.exports = nextConfig
With this, you’re all set!
To test your project, simply start your Next.js development server by running npm run dev
on your terminal. Then, in your browser, navigate to the website address on your local server — it should be at http://localhost:3000. You should be able to see the headers you set in the network tab of the developer console, like so:
In this article, we looked at security headers and how they can be implemented in a Next.js application. For information about HTTP Headers, check out the MDN documentation.
See you all next time!
Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your Next.js apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
One Reply to "Using Next.js security headers to strengthen app security"
Thank you for a great explanation about how to specify security headers in a Next.js app.