janko / rodauth-rails

Rails integration for Rodauth authentication framework
https://github.com/jeremyevans/rodauth
MIT License
593 stars 40 forks source link

`rescue_from ActionController::InvalidAuthenticityToken` does not work #198

Closed renchap closed 1 year ago

renchap commented 1 year ago

In our app, we want to catch CSRF errors to be able to store more details about what is triggering them and return a custom response to the user.

We are using a rescue_from ActionController::InvalidAuthenticityToken do … end custom block in our ApplicationController

This works fine for non-rodauth calls, but Rodauth calls are ignoring this rescue_from and raise the exception all the way to Rack.

For example, you can reproduce with this branch: https://github.com/renchap/rodauth-demo-rails/tree/rescue-csrf where I removed the CSRF HTML tags.

A request with failed CSRF looks like this:

Started POST "/logout" for ::1 at 2023-03-07 21:30:51 +0100
  Sequel (1.0ms)  DELETE FROM "account_active_session_keys" WHERE (("account_id" = 1) AND ("last_use" < (CAST(CURRENT_TIMESTAMP AS timestamp) + make_interval(secs := -1209600))))
  ↳ app/misc/rodauth_app.rb:7:in `block in <class:RodauthApp>'
  Sequel (0.6ms)  UPDATE "account_active_session_keys" SET "last_use" = CURRENT_TIMESTAMP WHERE (("account_id" = 1) AND ("session_id" = 'yCezI-K7rYN4V-JxyuvcMq_-jZnnnxN0ymxLPITWDwM'))
  ↳ app/misc/rodauth_app.rb:7:in `block in <class:RodauthApp>'
Can't verify CSRF token authenticity.

ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.):
janko commented 1 year ago

Hi @renchap, thanks for reporting. Yeah, unfortunately, Rodauth checks the CSRF token before calling the _around_rodauth hook (code), which is where rodauth-rails activates rescue_from handlers for the given block. I'm guessing that was a deliberate design decision, to not consider the Rodauth action started before the CSRF token was checked. So, ActionController::InvalidAuthenticityToken is probably the only exception for which a rescue_from handler won't work for Rodauth actions.

You should be able to work around it by repeating the same error handling around Rodauth route handling inside the Rodauth app's route block:

class RodauthApp < Rodauth::Rails::App
  route do |r|
    handle_invalid_csrf do
      r.rodauth
    end
  end

  private

  def handle_invalid_csrf
    yield
  rescue ActionController::InvalidAuthenticityToken
    Rails.logger.error("invalid CSRF!")
    request.halt [422, {}, ["Invalid CSRF"]]
  end
end
renchap commented 1 year ago

Mmh ok, this is rather unfortunate but I should be able to work with this.

My end goal here is to send debug data to Sentry when this exception raises, because I have a user unable to sign into our app on one specific computer. After a lot of debugging, I ended up finding that this is caused an an invalid CSRF token, but I dont know why the token is invalid, so I am trying to gather all data used by ActionController to compute it and send it all to Sentry so I know exactly why it is invalid.

I already lost many hours due to this bug, and I hope to be able to understand exactly what is happening here…

janko commented 1 year ago

As this doesn't appear to be caused by rodauth-rails, I will close this issue. Hopefully you'll find the root cause 🤞🏻