Welcome (back) to my technical series on Security for Fullstack Devs. If this is your first time joining us, then please check out article one in the series here. The first article is a checklist of all of the considerations we should make when starting a new web app. In this article, we’re going to look at the best practices I’d recommend for securing your Web App at the Server level — from both the Frontend and the Backend.
The Web Server is arguably the most influential choice one can make when embarking on a new web project. Not only for security reasons, but for things like performance & reliability. We need our Server to be blazing fast, but we cannot trade-off security in doing so. One of the big, and often the most common issues here, is the fact that often we simply launch the default Server used by our chosen framework or piece of technology, without actually knowing much (if anything) about the Server itself, or without including any security features what-so-ever.
That being said, we can’t view security as the only priority on our list. (Although it should be at the top). As Fullstack and Frontend Developers, we have rather a lot of considerations to make including, as stated above, performance and reliability. If any of the Security features I touch-on in this article will impact on either of those factors, I’ll make sure I mention that and include any pitfalls you may encounter.
In my last article, I briefly touched on subjects such as XSS and HTTPS. In this article, I’d like to show you, in more depth, how we can mitigate these issues, alongside some more key points such as HSTS (Strict Transport Security), CSP (Content Security Policies), and referring back to taking a Tech Blueprint in my last article; how the points I will cover aid that. So let’s dive in, and get on with ensuring our Web App is as secure as possible starting from the Server, outward.
Strict Transport Security (HSTS)
HSTS is a security header that allows us to enforce HTTPS across our entire Web App. If you read my previous article, you’ll remember I advocate the idea of HTTPS everywhere, and showed you how to get a trusted, secure SSL certificate free-of-charge from Let’s Encrypt. The reason we need HTTPS everywhere is that our users are vulnerable to Cookie stealing and Man-in-the-middle attacks if we don’t have it implemented.
Now, as you’re probably aware, simply owning an SSL Cert will not immediately make all of your Web App HTTPS only — we need to tell our App to do that ourselves. One of the best ways of doing this is by using the HTTP Header of HSTS. By using this Header, we can force all traffic on our App to use HTTPS and upgrade non-HTTPS. This Header may also even provide a performance boost, as we no longer would have to send our users through a manual redirect.
So, you’re probably thinking “Wow! I need this!”. Well, whilst I agree — alongside the Content Security Policy I’ll talk about later, this needs to be implemented with caution. Allow me to explain… Here’s what a sample HSTS Header looks like:
Strict-Transport-Security: max-age=630720; includeSubDomains; preload
And in Node.js:
In this Header, we have 3 directives that apply.
max-age : By specifying a max-age, we are telling the user’s browser to cache the fact that we use only HTTPS. This means that if the user tries to visit a non-HTTPS version of the site, their browser will be automatically redirected to the HTTPS site, before it even sends a message to the Server. Therein lies the slight performance boost I mentioned earlier. Now, while this does sound fantastic in theory, what we need to be aware of here, is the fact that if a user ever needed to access a non-HTTPS page, their browser simply won’t let them, until this
If you are going to activate this feature, and set a long
max-age, (required by the pre-load sites I’ll talk about in a second), you really need to be sure that you have your SSL cert setup correctly, and HTTPS enabled on all of your Web App before you take action!
includeSubDomains : The
includeSubDomains directive does exactly what it says on-the-tin. It simply offers additional protection by enforcing the policy across your subdomains too. This is useful if you run a Web App that sets Cookies from one section (perhaps a gaming section), to another section (perhaps a profile section), that need to be kept secure. Again, the issue with this lies similarly to the above, in that you must be sure every subdomain you own and run, is entirely ready for this to be applied.
preload : The most dangerous directive of them all! Basically, the
preload directive is an in-browser-built directive that comes straight from the browser creators. This means that your Web App can be hard-coded into the actual Browser to always use HTTPS. Again, whilst this would mean no redirects, and therefore a performance boost, once you’re on this list; it’s very difficult to get back off it! Considering that Chrome takes around 3 months from build-to-table, and that’s only for the people who auto-update, you’ve got a huge wait-time if you make a mistake.
So we have ourselves here an incredibly powerful, yet actively quite dangerous Security feature. The key here is ensuring you know your HTTPS measures inside-out, and using discretion. Whilst I don’t recommend you submit your site to the
preload directive, if you wish to – you can here.
Note — it is not a requirement to use preload to utilise HSTS. The only Header you need apply is the `max-age` header.
If you are going to use the HSTS protocol, start out with a small
max-age – something like a few hours, and continue to ramp it up over a period of time. This is the official advice Google Chrome give. If you use the
includeSubDomains directive, be sure you don’t have internal (company.mysite.com) subdomains that would be unreachable if affected. If you’re going to submit your Web App to
preload, follow the official guidelines, and make sure you know exactly what you’re doing – (which I’m not entirely confident of myself!)
Using the X-XSS-Protection Header
As I mentioned in my last article, XSS (Cross Site Scripting) is the most common of all Web App attacks. XSS occurs when a malicious entity injects scripts to be run, into your Web App. A few years back, most web browsers added a security filter for XSS attacks built into the browser itself. Now whilst in theory this was a good step, they did tend to throw-up false-positives quite often. Due to this, the filter can be turned off by the User. (And the option should be available, in my opinion.)
To ensure our Users are protected, we can force this filter (worth it), on our Web App by using the
X-XSS-Protection Header. This Header is widely supported by common browsers, and something I’d recommend using every time.
To apply this header to your Node.js app, you should include the following:
Note the two directives in this header:
1 is simply acts as a Boolean 1 or 0 value to reflect on or off.
mode=block will stop the entire page loading, instead of simply sanitising the page; as it would if you excluded this directive altogether.
If you’re a security-freak like myself, and a user of the Chromium browser, you could even go one-step further than this and set the directives like so:
X-XSS-Protection: 1; report=<reporting-uri>
Now, if the browser detects an XSS attack; the page will be sanitised, and a report sent of the violation. Note that this uses the functionality of the CSP
report-uridirective to send a report that I will talk about in the Content Security Policy section below.
Defend against Clickjacking
Clickjacking occurs when a malicious agent injects objects / iFrames into your Web App, made to look like your Web App, that actually send the User to a malicious site when clicked. Another common, and possibly more scary example is that malicious agents insert something that looks like a payment form into your Web App, that looks realistic, but steals payment details.
Now, whilst this could be a very dangerous issue, it’s very easy to mitigate, with almost no impact on your Web App. Servers offer Browsers a Header Protocol named
X-Frame-Options. This protocol allows us to specify domains to accept iFrames from. It also allows us to state which sites our Web App can be embedded on. With this protocol, we get three fairly self-explanatory options/directives:
If we choose
DENY, we can block all framing. If we use
ALLOW-FROM, we can supply a list of domains to allow framing within. I tend to use the
SAMEORIGINdirective, as this means framing can only be done within the current domain. This can be utilised with the following:
Content Security Policy (CSP)
CSP is another major topic when it comes to Server-Browser security for Web Apps. At a high-level; Content Security Policies tell the browser which content is authorised to execute on a Web App, and which will be blocked. Primarily, this can be used to prevent XSS, in which an attacker could place a
<script> tag on your Web App. The Content-Security-Policy is a Server-Browser header that we can set to ensure our Server tells the Browser exactly which media, scripts, and their origins, we will allow to be executed on our Web app.
The whitelisting of resources and execution URIs provides a good level of security, that will in most parts, defend against the majority of attacks.
To include a Content Security Policy that allows only internal and Google Analytics, in an Express.js server; you could do the following:
However, if we do not wish to allow any external sites to execute scripts on our Web App, we could simply include the following:
script-src directive here, that we have set to
self, therefore only allowing scripts from within our own domain. Of course, CSP is not without its own problems. Firstly, it would be very easy for us to forget about some of the media we have in our Web App and to simply exclude them accidentally. Now that the web is so rich in media, this would be reasonably easy to do. Secondly, many of us use third-party plugins on our Web App. Again, unless we have a full blueprint of these, we could very easily block them.
So, once activated, this Server Header could potentially be very detrimental to us. However, there are two great ways of testing this. You can set a strict policy, and use the built in directives;
report-uri to test them. The
report-uri directive tells the Browser to send a JSON report of all of the blocked scripts to a URi that we specify. The
report-only directive does the same, but will not block the scripts on the site. This is very useful for testing, before we put this Header into production.
There’s a good write-up on the reporting, here .
Content Security Policies are great, but must be used cautiously. Much the same as HSTS mentioned above, we need to ensure we are aware of the ins-and-outs of the situation before activating. If you are loading in external images, scripts etc. you need to understand that unless you include these in the policy, they will be blocked.
Disable Caching of Sensitive Data
I’ve utilised Caching on almost every Web App I’ve ever built. As we know, it saves Server load and improves load-times for our Users to a huge degree. But, like most things, it does have its downsides. For instance, many people force aggressive Caching on pages of Web Apps that contain sensitive data. By Caching sensitive data, we could allow a malicious agent to access private details of our Users.
It makes sense for us to ensure our Server tells the Browser not to Cache any pages that contain sensitive data i.e. User payment details pages or something similar. Happily, we can do this quite simply by utilising the
Cache-Control header. For example in Node.js, you may include the following on Sensitive pages:
By doing this, we are telling the Browser that the page must not be cached, and that it must re-validate the page on reload. The
Expires directive is allowing us to specify a timestamp after which the page is considered totally invalid. I just enter a simple long-past date here.
So, from enforcing HTTPS with Strict Transport Security, to securing our Web App with a Content Security Policy; we’ve covered the main topics, in my opinion to ensuring Server-Browser security for our Web Applications. These topics are all techniques I utilise myself and would advocate for use in your applications on an ongoing basis.
One topic I have not been able to cover in this article is CSRF — Cross Site Request Forgery. This is such a big topic, that I think it needs it’s own article — hopefully, I’ll be able to supply that for you in the near future! Alongside implementing Server Security best practises, keeping up to date with vulnerabilities for your chosen Server and language is also mega important. The best places to do so are the GitHub repo of the Server and CVEDetails.com.
The last article in this series is going to cover a huge element of Web Security — Users, Authentication & Authorisation from both a front-end and back-end perspective. I hope you’ll join me back here on the LogRocket blog, in the coming weeks!
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.