First of all, what is HTTP strict transport security (HSTS) and why do you need it? HSTS is a specific HTTP response header that tells the browser to load a site over HTTPS. The browser will do so whether the user uses the HTTP or the HTTPS protocol. Even if you have a redirect, it's a good idea to set up HSTS since that initial plain text send from the browser to the HTTP endpoint remains vulnerable. That's because the browser will still send any domain cookies to that endpoint—unless, of course, they're set to secure.
In this post, you can read more about HSTS and how it relates to React apps. You'll learn a few ways to enable it, whether you're serving your app via Next.js, Express, or some other means. Note that the term "React.js" will be used at times rather than simply "React," in order to distinguish it from the React ecosystem that includes React Native. First up: HSTS.
More Info About HSTS
The Internet Engineering Task Force (IETF) published the HSTS spec in 2012. Members from Google, PayPal, and others designed HSTS as a way in which web servers could direct user agents to use HTTPS only. With HSTS enabled, users can't bypass any issues with the certificate.
But the real reason for HSTS is to protect cookies—especially session cookies—from being hijacked when and if the site is first loaded over HTTP. It does this by telling browsers (user agents or UAs) to load the site only using HTTPS even when the user enters the HTTP URI scheme rather than HTTPS. Even if you redirect users from HTTP to HTTPS, the initial hit is over plain text and the cookies can be seen by attackers.
An HSTS header is relatively simple. It looks like this:
Strict-Transport-Security : max-age=3600 ; includeSubDomains
The user agent will cache the HSTS policy for your domain for max-age seconds. When the user visits your site, the browser will check for an HSTS policy. If it finds it, then boom! It uses HTTPS even if you went to http://example.com instead of https://example.com.
From now on we're going to focus on enabling HSTS for React apps.
Setting HSTS on the Web Server
Setting HSTS headers on the web server is my first recommendation. Since this is not application code, it doesn't really belong to the React app itself. This is in line with the principle of separation of concerns, meaning you should keep separate concerns in their own domain. Servicing web traffic is the web server's concern, not that of the application itself.
For example, if you're using NGINX to serve your React.js app, you can set headers in NGINX. Add the following to the nginx.conf file:
http {
...
add_header Strict-Transport-Security : max-age=3600 ; includeSubDomains
...
}
This directive uses the HTTP headers module to add arbitrary headers to responses. Other servers will have their own way to add headers, but it's a common feature of web servers. Alternatively, most response headers can go at the server or location level.
You might instead be serving your React.js app via a CDN. You will still want to protect the user cookies by ensuring they're sent only over HTTPS. This is true even if your app comes from a CDN. From my own travels, I know you can set up an Amazon S3 bucket to deliver an app payload, but this is not an HTTPS site by default. For HTTPS hosting you need to use Amazon CloudFront. Even still, I looked into several popular CDNs (AWS, GCP, CloudFlare) and they don't allow us to set the strict transport security header. So if you're distributing your React.js app via a CDN, you might have other means at your disposal for making sure it's only served via HTTPS. For example, CloudFront has the option to block HTTP traffic and only serve HTTPS.
Now, we've gone over a few options for client-side React.js. It's fairly common, however, to do some kind of server-side rendering to deliver a React web app that's locked, loaded, and ready to rock. Let's see how to add the HSTS headers in an SSR'd React web app.
How to Set HSTS in an SSR React App Like Next.js
Next.js is a fairly common way to deliver a server-side-rendered React.js app. It even has its own server, which you should probably have behind something like NGINX or another production-grade load balancer anyway. But if you have some reason to add the HSTS in Next.js, you can definitely do it that way too. Here's how to accomplish this feat!
In your app, open the next.config.js file and add the headers directive there like so:
module.exports = {
async headers() {
return [
{
source: '/(.*),
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=65540 ; includeSubDomains'
}
]
}
]
}
}
Hadouken! --->>)) That's a lot of indentation, but it'll get the job done. You can add other site-wide headers there as needed too. But should you? The creators of Next.js think not.
Vercel, the official Next.js hosting solution, adds the HSTS headers automatically. Nothing to do there! I suspect they're probably using something like NGINX behind the scenes to deliver your apps. If so, they're probably setting it just as I've pointed out above in the NGINX config section.
ReactDOMServer is another approach to SSR, so let's talk a bit about how you might add HSTS headers when you use ReactDOMServer. Well, if you're using ReactDOMServer, you're probably using something like Express to serve the rendered app. While it's entirely possible to add custom headers to responses, there's a more concise approach. A third-party middleware named Helmet has the ability to add HSTS headers and many other security headers with the greatest of ease. Use Helmet for Express like this:
First, install while adding the dependency to package.json in one fell swoop:
npm i helmet --save
Then, simply import and add the middleware:
const helmet = require('helmet')
// ...make your express app...
// add HSTS headers only
app.use(helmet.hsts())
// or all the headers helmet offers
add.use(helmet)
// CAUTION!!! some header settings -- including HSTS -- may
// cause MAJOR borkage if your site is not prepared!
Cannot Be Set Via Client-Side React
You can't set HSTS in client-side code since it's a server response header. That said, you might be wondering if there's anything you need to do with your client-side React.js code. In short: no. The response header is handled by the browser itself. This wouldn't really apply to React Native apps where you should probably be using HTTPS in your URLs in any case. I usually recommend using a variable to hold the scheme and host of the API URLs so it's quite a bit easier to change between different servers (e.g., dev and test servers). There isn't much else to be said about client code, so let's round this post off with a summary of the main points.
Summary of the Main Points
At some level, you'll need to set these headers on the server. Now, whether you are doing this on NGINX, a gateway, your hosting platform, or your preferred SSR server to set it is up to you. I prefer setting it up on the server alongside the TLS certificate. Since HSTS relates somewhat closely to the TLS cert, it makes the most sense to me to have it right in the same place. Even still, you have options so you can choose what's best for your own situation.
One final important thing: be careful when setting this up! Your users might get stranded if you aren't properly prepared. They won't be able to click through the security gate in their browser.
This post was written by Phil Vuollet. Phil leads software engineers on the path to high levels of productivity. He writes about topics relevant to technology and business, occasionally gives talks on the same topics, and is a family man who enjoys playing soccer and board games with his children.