Security for fullstack web developers — Part 3

Thanks for joining me for the third part of my technical series on security for fullstack developers. In the first article, I outlined a security protocols checklist that we should always consider when starting a new web app. In article two, I went into more depth on issues such as strict transport security (HSTS) and content security policies (CSPs).

In this article, I’d like to look into the elements of web app security that are often the most dangerous — those involving users and sessions. From a backend perspective, users & authentication are two of the deepest attributes to our web app, but I’d like to look at those attributes from a frontend perspective. Although dealing with issues like user credential security are incredibly important, often we tend to overlook to the importance of frontend user & session security issues such as cookie theft and cross-site request forgery (CSRF).

In my first article I talked about such cookie protocols as the Secure attribute and the httpOnly attribute. In this article, I’d like to look at protocols such as the Expiration attribute, to coincide with user session security. Alongside a look into sessions, I’d like to propose an alternative to using traditional sessions in the form of JSON web tokens (JWT). I will also discuss one sure-fire way to prevent CSRF attacks.

Since my last article promised a CSRF solution, let’s start by discussing exactly what it is and how I believe we can entirely mitigate it in a modern web application.


Cross-site request forgery has been at the forefront of web app security for longer than any of us care to remember. How does it work? Essentially, a malicious agent sends a (forged) request from one app to another while the user is signed in and authorised. The malicious agent enters and alters restricted actions on the requested app, with the requested app believing entirely that the alterations are legitimate.

Let me show you.

Imagine, if you will, that I am security-abusing miscreant. Imagine, also, that I happen to know that Twitter has no CSRF protection. (It does, this is merely an exercise. Don’t get any ideas.) I’m also aware that most people who visit my web app probably leave their Twitter logged in. That means they have a cookie stored in their browser.

On my web app, I could embed something like this:

When a browser loads my web app, it will also load this form (entirely invisibly. I would then also have embedded a small piece of JS to POST the form without you ever knowing:


Cool. I’ve just sent a tweet on your account, without ever having to know your username or password. The cookie you had stored in your browser allowed my app to send a forged request, pretending to be you — and if Twitter had no CSRF mitigation, it would have worked too!

Perhaps a better way to relate this back to your own web app would be to use the scenario in which, like most of us, you probably have a route setup for your own users to alter their user profile. /user/profile is probably the most common example, and given that most security-abusing miscreants know this (myself included), it would be trivial to send a POST request to your web app, updating the user’s profile with a new email address.

While this may not seem like an obvious problem, once the user’s email address is changed, I could then issue a password reset for that user account. The new password, of course, goes to whatever email address I dropped in the user profile. At this point, the user account is completely compromised. Any sensitive data is now in my hands and, lest we forget, I’m a miscreant. Scary stuff.

For years, we have been trying to solve CSRF requests by checking HTTP headers such as the Origin and Referer. Whilst these have offered fairly robust protection for a few years, there is now a simple directive that, once applied, will entirely mitigate CSRF attacks.

Enter the SameSite cookie directive. SameSite is relatively new and remains widely unknown. In essence, the SameSite directive, once applied, will tell the browser to never send that cookie when a request from an external (cross-site) URL is made. We can apply this directive by altering our cookies as such:

Set-Cookie: sess=sessionid123; path=/; SameSite

It really is that easy. I wouldn’t recommended removing your existing CSRF protection just yet, but I would definitely recommend including this directive on your web app.


As I mentioned in the intro, my first article in this series talked about a couple of cookie directives — namely HTTPOnly and Secure . As we know, cookies are an important feature of our web applications, carrying data mainly referring to our user sessions. While simply implementing the aforementioned directives is sufficient in securing your cookies, and preventing attacks, we can actually take cookie security a step further.

Cookie prefixing is a relatively underused technique that we can utilise to ensure a cookie is secure:

The __Secure prefix - If a cookie’s name begins with “__Secure”, the cookie MUST be:

  • Set with a “ Secure ” attribute
  • Set from a URL whose scheme is considered secure by the user 

The following cookie would be rejected when set from any origin, as the “Secure” flag is not set:

Set-Cookie: __Secure-sess=12345;

While the following would be accepted if set from a secure origin e.g. https:// and rejected otherwise:

Set-Cookie: __Secure-sess=12345; Secure;

Alongside the __Secure prefix, we also have the __Host prefix:

The __Host prefix - If a cookie’s name begins with “__Host”, the cookie MUST be:

  • Set with a “Secure” attribute
  • Set from a URI whose “scheme” is considered “secure” by the user agent
  • Sent only to the host which set the cookie. That is, a cookie named “__Host-cookie1” set from “ ” MUST NOT contain a “Domain” attribute (and will therefore be sent only to “”, and not to “”)
  • Sent to every request for a host. That is, a cookie named “__Host-cookie1” MUST contain a “Path” attribute with a value of “/”

The following cookies would always be rejected:

Set-Cookie: __Host-sess=12345
Set-Cookie: __Host-sess=12345; Secure
Set-Cookie: __Host-sess=12345;
Set-Cookie: __Host-sess=12345;; Path=/
Set-Cookie: __Host-sess=12345; Secure;; Path=/

While the following would be accepted if set from a secure origin e.g. https:// , and rejected otherwise:

Set-Cookie: __Host-sess=12345; Secure; Path=/

By setting these prefixes, any compliant browser will be made to enforce them.

Now, if we include the tips from my first article, and the tips above, we can make the most secure cookie possible:

Set-Cookie: __Host-sess=id123; path=/; Secure; HttpOnly; SameSite

In this most-secure-cookie, we’re utilising the __Host prefix which means the Secure attribute has to be set, and it must be served from a secure host. There is no Domain attribute set and the Path is /. We’ve set HttpOnly for XSS protection, and SameSite is enabled to prevent CSRF. Of course, this won’t be the best or most practical solution for a lot of people, but it is the most secure cookie we could set from our web app in theory.


Sessions are used to pass information around your web App pertaining to a specific User. When a user is logged in, or their browsing of your web app begins, they are assigned a session ID (or token) that is used by your web application to identify them as they move around and perform different actions.

The idea of session-jacking/cookie theft is age-old, something that can be mitigated fairly easily, and is solved almost by default by modern web frameworks.

The concept behind session-jacking is that a malicious agent can take the session ID set by your web app’s cookies and use this to repeat actions that should only be available under the original session. Even though modern web frameworks prevent this sort of thing, it’s worth noting — especially if you still want to roll-your-own.

Firstly, the session ID name must be generic. In web frameworks of old, this was often set to something very specific such as ASP.NET_SessionId and PHPSESSID. The reason this isn’t okay is that a malicious agent can tell immediately which web stack you’re running and therefore find exploits much quicker.

Secondly, the Session ID content (value) must be meaningless to prevent information disclosure attacks where a malicious agent is able to decode the contents of the ID and extract details of the user, the session, or the inner workings of the web application.

It is recommended to create strong session IDs through the usage of cryptographic hash functions such as SHA1 (160 bits).

Alongside setting secure attributes, it’s strongly recommended to set an Expire and Max-Age for your sessions if you present sensitive data. Information-critical providers (like banks), would be wise to have sessions expire after ~20 minutes of inactivity. While this may seem extreme, I mention this because my bank only lets me sit idle for 5 minutes before it destroys the session and has me login again.

In express.js, you can set the cookie expiry with the following:

res.cookie(name , 'value', {expire : new Date() + 9999});

Note, the 9999 is the time to expire in milliseconds.

Sessions vs JWTs

Although I’d class the sessions talked about above as a front-end topic, from a full-stack perspective, the introduction of JSON web tokens has offered us a way around the storage of session IDs in cookies that we’ve all used forever.

In essence, a JWT is a stateless way of transmitting information between parties, much as we would send around the session ID in a cookie. Arguably, a JWT is more secure as it is digitally signed and therefore be both verified and trusted.

JWTs work in a very similar way to a session cookie: A user logs in, a success is returned alongside the user object, that user object is securely encoded into a JWT that we then pass around our web app instead of a session ID in a cookie.

I won’t go into an enormous amount of detail in this article on JWTs, but I thought they were rather worth mentioning. If you’d like to read about the benefits and trade-offs of utilising JWTs instead of sessions, there is a fantastic article about the topic here.

If you’d like to check out more about JSON web token basics and how to get started with them, take a look here.

Alternative frontend defences for sessions

Web apps can complement the previously described session management defences with additional countermeasures on the frontend. Even though client-side protections, typically in the form of JavaScript checks and verification, are certainly not bulletproof and can easily be defeated by a skilled attacker, they can introduce another layer of defence. The more layers of defence, the better.

Forcing session kills on browser actions such as page refresh, clicking back/forward or closing the window is a common way that high-security applications ensure sessions remain safe. Destroying a session when a user performs any of these actions is something we don’t need to consider for the average web app, but if you are serving sensitive information it is definitely worth considering.

As mentioned above, enforcing a Max-age on a cookie is a good way of ensuring a session won’t remain, but we also need to integrate some JavaScript on the frontend to destroy the session when the expiration time is reached.

A very obvious point, but one that I have seen ignored before, is ensuring a user can manually logout of a session at any point. Including a logout button / link is pretty much second-nature to most of us (and our modern web frameworks), but it is still somehow ignored by some.


We’ve covered some really big topics in this article and I’d like to thank you personally for joining me for this series. I sincerely hope you’ve learned plenty and have implemented some of the security protocols I have advocated on both the front and backends of your web apps.

By utilising the methods and resources I have mentioned throughout this series, you would be in the top 90% for web app security. Many ignore security altogether, or simply make it a very-last priority. I hope that by giving practical, simple solutions and snippets, we can all create a more secure environment for our web apps without too much developer effort.

As mentioned in the last two articles, keeping up-to-date with security practises is your best friend. The best tools in web security are common sense and vigilance.

If you ever have any questions, or need to reassure yourself on a security issue, please reach out to me!

Thanks again for sticking with me. Take care!

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.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.