Rails + Rack::Csrf
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.
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
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
exception and the app 500s. (Alternatively, we can have the app 403 by removing the
raise: true option).