Do you ignore your spam emails? To be honest, they could be more dangerous than you think. Be cautious when visiting a website flooded with advertisements and clickbait. An attacker behind the screen may trick you into doing something malicious, such as deleting your account on a website, transferring funds illegitimately, and so on. These are all possible outcomes of a CSRF attack.
Other names for these attacks are "one-click attacks" or "session riding." CSRF attacks aren't common these days. But understanding how they work is vital if you want to build secure services and web applications. And even in the past few years, CSRF attacks have gotten well-known companies into trouble.
In this roundup, I'll help you understand what CSRF is and how a CSRF attack may happen. We'll look at an example. Then, I'll walk you through how you can protect your React application from such an attack.
A Bird's-Eye View of CSRF
CSRF stands for cross-site request forgery. Let's break down that term.
The "cross-site request" part simply means a request sent from site A that was supposed to be sent from site B. This doesn't sound that bad, right? Well, only if I authorized that request.
For instance, it's fine if I delete my Firebase account from my Google account. However, if I were to do the same using my random XYZ account, chances are that my Firebase account is compromised.
The next question is: Why would I do that? Why would I want to delete my Firebase account using some other random website that has no correlation with it?
There could be a couple of use cases that cater to this scenario. For instance, I might authorize my Google Cloud account to delete my Firebase account. Similarly, I might authorize my Facebook account to delete my Instagram account. However, if I visit a random website that wipes out my Instagram account, I'd be concerned about the security of my social media handles.
The other part of the term, "forgery," means forcibly and illegally carrying out an action you aren't authorized to do.
So if you put two and two together, CSRF or cross-site request forgery means an unknown application forges a request to your server. But how does an attacker send a request on your behalf?
A CSRF Attack in Action
Now that you have a good idea of what CSRF really means, let's look at how an attacker might execute a CSRF attack on your application.
For the purpose of this example, let's say you've got a web application with a ReactJS front end that interacts with the back end server.
Let's say your application has a simple home page and a profile page. The home page of your application is visible to anyone on the web. For brevity, the following application shows a simple page that lists a couple of users.
Home Page Demo.
However, in order to access the profile page, a user must be authenticated on the app. Inside the profile page, there's a small button that enables the user to delete their account. Let's say the profile page looks like this.
Profile Page Demo.
Let's say your user tries to log in to your application using a login form. The user fills in this form to validate her credentials from the server. Like most typical authentication flows, the server sends a cookie that's used to manage the session of the user. This cookie is stored in the browser and is sent back with every request to validate the authenticity of the user.
Let's say a user wants to delete her account on your site. To do this, she must click a Delete button. However, only a user who has signed in to the application can perform this action.
When the user presses Delete, the client sends a delete request to your server. The server processes this request and carries out a delete operation on your database. Your delete request would look somewhat like this:
CSRF Attack Request.
To validate the authenticity of the delete request, the user's browser stores the session token as a cookie. However, this leaves a CSRF vulnerability in your application. An attacker can send a delete request to your server with the cookie present in the browser. All they need you to do is open a link with a hidden form that triggers this delete request in the background. Let's see how this works.
In this example, the triggering point for the attack is opening a URL. The attacker generates a URL that points to another web application. The attacker then uses social engineering to open that URL in the user's browser.
As soon as the application loads, it gets access to the session cookie stored in your browser. And that's it! The attack could be triggered under the hood, in the background, while the malicious link loads.
Aftermaths of the Attack
Your user would have no idea that she was under a CSRF attack! Eventually, though, she'd question your application's credibility and might not want to use your app again.
The scale of this attack may be huge, which makes it even worse for you if the attacks delete the accounts of a large number of users. This makes your product look weak and eventually affects your business. You may lose a ton of users—and if the word gets out, you may lose some potential users as well.
Hence, it's important to safeguard your system from a CSRF attack. Let's see how you can do so.
CSRF Protection: Myth Busters
To understand how you can protect your application from a CSRF attack, you must first understand the solutions that aren't reliable. These solutions seem easy, but an attacker can easily bypass them. And your application might still be vulnerable to a CSRF attack.
Let's have a quick glimpse at these:
Using Web Storage Instead of Cookies
Do you think you can store the authentication tokens inside the browser's local or session storage instead of cookies to solve this problem? Think again. The attacker can access any data you store on your browser's local storage by running the following line of code:
And once the attacker gets access to your session token, you're back to square one! Sure, this might add another blocking step for the attacker, but it definitely isn't a reliable solution.
Using a POST Request
If you refactor your server endpoints and make every endpoint a POST request, you're still not completely safe from a CSRF attack. In the previous section, I illustrated an example of a delete request to delete the user's account. This could have been a GET request as well.
You might think that using a POST request will add another pain point for the attacker to figure out the request body and parameters. However, it's still merely another barrier and not a foolproof solution.
CSRF Protection: The Reliable Solution
Let's go through the steps you can follow to protect your application against a CSRF attack.
Using CORS on the Server
CORS stands for cross-origin resource sharing. It's a protocol that allows your client to send requests and accept responses from a server that has a different origin. Normally, the browser uses an SOP or same-origin policy to ensure that your server only listens to requests from clients of the same origin.
However, sometimes you want to expose some public API endpoints of your server so different clients can access it. Or maybe you simply wish to host your server and client on different domains. In these scenarios, the browser's SOP doesn't allow your server to communicate with your client as a security measure.
CORS lets you work around that problem so your server can communicate with clients of different origins. This is possible if your server has the following line of code inside the request handlers or middleware.
If you're new to CORS and want to understand what it is and how it works, you can check out my other post where I talk about CORS in detail.
Using CSRF Tokens
CSRF tokens, also called anti-CSRF tokens, let your server communicate to the client before an authenticated request is made that may be tampered with. Let's go back to the previous example, where an attacker sent a delete request from a client from your browser.
Let's say you have a NodeJS and Express back end that interacts with your React client. You can install a library called csurf that's used to generate CSRF tokens, and you can send them to your client through an endpoint.
npm i csurf
Now you need to add the following endpoint.
The above is a simple GET endpoint that returns a CSRF token.You can send a GET request to that endpoint to retrieve the CSRF token. I'm using Axios in this example, but you can also use Fetch API to send valid headers with the X-CSRF-Token attached to the request.
Let's say your minimal profile page component in React looks like this.
You can then call the getCSRFToken function inside the useEffect as shown:
That's it! This CSRF token is sent alongside every request, and it generates every time your profile page loads.
However, you need to make sure you don't have any XSS vulnerabilities in your application that can leak these tokens to the attacker. React naturally protects you from XSS attacks, but here's an interesting guide that may help you if you wish to know more.
I hope you got the hang of safeguarding your applications from a CSRF attack. Here's a detailed guide on CSRF. I highly recommend you go through it so you can understand things better from a generic perspective. You must always validate the origin of requests that your server receives. You can create a custom middleware that does this. Finally, always check for other security vulnerabilities in your application and routinely refactor your code accordingly. Until next time!