This post assumes you’re familiar with CSRF attacks:

Here, I will go over how to use Rack::Csrf in a Rails app. That might leave a few people scratching their heads… why not just use built-in CSRF protection?

As your app it evolves, you might find good use in having middleware intercept certain requests. In Rails, CSRF protection happens at the controller layer, so it is completely possible that some of these requests will completely bypass it. Examples are always good: say you have a Rack::Proxy who’s job is to intercept requests going to /external_api/* and forward to an external web service. In cases like this, we need to set up a “catch-all system”—a system where the authenticity of all non-GET requests is being verified as coming from a valid source, even those that don’t hit the controller layer of the Rails app.

💢 Rack::Csrf 💢

This is just a small Rack middleware whose only goal is to lessen the hazards posed by CSRF attacks by trying to ensure that all requests of particular types come from the right client, not from a mischievous impersonator. (Source: Github)

CSRF Token Generation and Association with the Current Session

Since we’re going to use Rack::Csrf, we can remove the line protect_from_forgery from our application controller, preventing forgery protection from happening at Rails’ controller layer. However, removing this line does not necessarily prevent Rails from generating a CSRF token for the current user and associating it with the current session; in fact, it’s possible for Rails or Rack::Csrf to do this. As a consequence, we have to make sure both Rails and Rack::Csrf are reading/writing from/to the same place. In my case, it was enough to configure Rack::Csrf to use _csrf_token (Rails’ default) as the key. In config.ru:

use Rack::Csrf, key: '_csrf_token'

Token Verification for non-GET requests

Going back to our Rack::Proxy example: how do you make sure all non-GET requests are being verified as coming from a valid source? Make sure Rack::Csrf comes after your session middleware—to verify the authenticity token—and before Rack::Proxy (i.e. before the request is intercepted).

With this configuration Rack::Csrf will intercept all non-GET requests. When the authenticity token in the request does not match the token associated with the current session, Rack::Csrf throws a Rack::Csrf::InvalidCsrfToken exception and the app 500s. (Alternatively, we can have the app 403 by removing the raise: true option).