Robin Percy Software & Content Fellow. Big fan of AI, Crypto, Databases, Erlang, Elixir, Go & Ruby.

Security for full-stack web developers :  Part 3

4 min read 1238

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).

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 (CSRF)

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 authorized. 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:

<form action="https://twitter.com/tweet" method="POST" id="sendTweet">
<input type="hidden" name="tweet" value="Hey!  Check out my awesome spam site - spam.com">

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:

document.getElementById("sendTweet").submit();

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.

Cookies

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 utilize 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
    agent

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

Set-Cookie: __Secure-sess=12345; Domain=myapp.com

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

Set-Cookie: __Secure-sess=12345; Secure; Domain=myapp.com

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 “https://example.com ” MUST NOT contain a “Domain” attribute (and will therefore be sent only to “example.com”, and not to “subdomain.example.com”)
  • 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; Domain=example.com
Set-Cookie: __Host-sess=12345; Domain=example.com; Path=/
Set-Cookie: __Host-sess=12345; Secure; Domain=example.com; 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 utilizing 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.

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.

Robin Percy Software & Content Fellow. Big fan of AI, Crypto, Databases, Erlang, Elixir, Go & Ruby.

One Reply to “Security for full-stack web developers :  Part 3”

Leave a Reply