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.
Get set up with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID
-
Install LogRocket via npm or script tag.
LogRocket.init()
must be called client-side, not server-side$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
- (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- NgRx middleware
- Vuex plugin
One Reply to "Security for full-stack web developers : Part 3"
Great article! Learnt a lot. Thanks for sharing your knowledge