Protecting Applications from CSRF attacks with Ruby on Rails

Lydia Gregory
2 min readSep 29, 2020

--

Construction workers with protective hats

What is a CSRF Attack?

CSRF stands for a Cross-Site Request Forgery attack. Through malicious links on websites, popups, emails, etc., users are tricked into unknowingly performing malicious actions of the attacker’s choosing.

Because it sneaks malicious actions into requests of users that are authenticated, I like the think of CSRF attacks as parasitic.

The damage that a CSRF attack can cause is related to (1) whether or not developers have added defenses against this type of attack and (2) the type of user account. For example, if the attack is achieved on a regular user’s account it can force the user to change their account information like their email or password, transfer funds, etc. If the attack is achieved on an administrative user’s account, the entire application can be put at risk.

Protecting against CSRF attacks with the Rails App

By default Ruby on Rails attaches a CSRF security token to the HTML document it serves up from its View. Using CSRF security tokens in the header prevents attacks because if they have to be validated by the backend server, attackers cannot create valid requests.

We send a CSRF security token with non-GET requests like Post, Update, and Delete. For non-GET requests we include headers. In these headers, we can send back the CSRF security token to verify it. The Ruby on Rails Application verifies the token on the server and throws an exception if the security token doesn’t match what’s expected.

Here’s what it looks like in practice:

onSubmit(event) {
event.preventDefault();
const url = "/api/v1/dogs/create";
const { name, owner, age} = this.state;

if (name.length == 0 || owner.length == 0 || age.length == 0)
return;

const body = {
name,
owner,
age
};

const token = document.querySelector('meta[name="csrf-token"]').content;
fetch(url, {
method: "POST",
headers: {
"X-CSRF-Token": token,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Bad network response");
})
.then(response => this.props.history.push(`/dog/${response.id}`))
.catch(error => console.log(error.message));
}

Using the Fetch API to send the our HTTP request, we can include "X-CSRF-Token": token as a header. The token, which we defined above the fetch call as const token = document.querySelector('meta[name="csrf-token"]').content, is queried from the metadata of the HTML document that Rails rendered by default when it served up the View.

--

--

Lydia Gregory

Full-Stack Software Engineer, Designer, and salsa dancer.