omniauth-rails_csrf_protection: token_verifier.rb:34 ActionController::InvalidAuthenticityToken

Please complete all sections.

Configuration

  • Provider Gem: omniauth-rails_csrf_protection 0.1.2
  • Ruby Version: 2.6.5
  • Framework: Rails
  • Platform: Heroku

Expected Behavior

Recent changes to csrf protection seem to cause a non-rescuable exception. Using

  • devise 4.7.1
  • omniauth 1.9.0
  • omniauth-google-oauth2 0.7.0
  • omniauth-oauth2 1.6.0
  • omniauth-rails_csrf_protection 0.1.2

application_controller.rb:

rescue_from ActionController::InvalidAuthenticityToken, with: :redirect_and_prompt_for_sign_in

protected

def redirect_and_prompt_for_sign_in
  redirect_to(new_user_session_path, alert: 'Please sign in.')
end

I expect that ApplicationController can catch the exception, but it does not.

Actual Behavior

An exception is raised with the following stacktrace.

/GEM_ROOT/gems/omniauth-rails_csrf_protection-0.1.2/lib/omniauth/rails_csrf_protection/token_verifier.rb:34

URL: https://myapp/users/auth/google_oauth2

/GEM_ROOT/gems/omniauth-rails_csrf_protection-0.1.2/lib/omniauth/rails_csrf_protection/token_verifier.rb:34 in call
/GEM_ROOT/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:209 in request_call
/GEM_ROOT/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:188 in call!
/GEM_ROOT/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:169 in call
/GEM_ROOT/gems/warden-1.2.8/lib/warden/manager.rb:36 in block in call
/GEM_ROOT/gems/warden-1.2.8/lib/warden/manager.rb:34 in catch
/GEM_ROOT/gems/warden-1.2.8/lib/warden/manager.rb:34 in call
/GEM_ROOT/gems/rack-2.0.7/lib/rack/tempfile_reaper.rb:15 in call
/GEM_ROOT/gems/rack-2.0.7/lib/rack/etag.rb:25 in call
/GEM_ROOT/gems/rack-2.0.7/lib/rack/conditional_get.rb:38 in call
/GEM_ROOT/gems/rack-2.0.7/lib/rack/head.rb:12 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/http/content_security_policy.rb:18 in call
/GEM_ROOT/gems/rack-2.0.7/lib/rack/session/abstract/id.rb:232 in context
/GEM_ROOT/gems/rack-2.0.7/lib/rack/session/abstract/id.rb:226 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/cookies.rb:670 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb:28 in block in call
/GEM_ROOT/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:98 in run_callbacks
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb:26 in call
/GEM_ROOT/gems/airbrake-9.5.0/lib/airbrake/rack/middleware.rb:32 in call!
/GEM_ROOT/gems/airbrake-9.5.0/lib/airbrake/rack/middleware.rb:21 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb:61 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb:33 in call
/GEM_ROOT/gems/railties-5.2.3/lib/rails/rack/logger.rb:38 in call_app
/GEM_ROOT/gems/railties-5.2.3/lib/rails/rack/logger.rb:26 in block in call
/GEM_ROOT/gems/activesupport-5.2.3/lib/active_support/tagged_logging.rb:71 in block in tagged
/GEM_ROOT/gems/activesupport-5.2.3/lib/active_support/tagged_logging.rb:28 in tagged
/GEM_ROOT/gems/activesupport-5.2.3/lib/active_support/tagged_logging.rb:71 in tagged
/GEM_ROOT/gems/railties-5.2.3/lib/rails/rack/logger.rb:26 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb:81 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb:27 in call
/GEM_ROOT/gems/rack-2.0.7/lib/rack/method_override.rb:22 in call
/GEM_ROOT/gems/rack-2.0.7/lib/rack/runtime.rb:22 in call
/GEM_ROOT/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb:29 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb:14 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/static.rb:127 in call
/GEM_ROOT/gems/rack-2.0.7/lib/rack/sendfile.rb:111 in call
/GEM_ROOT/gems/actionpack-5.2.3/lib/action_dispatch/middleware/ssl.rb:74 in call
/GEM_ROOT/gems/railties-5.2.3/lib/rails/engine.rb:524 in call
/GEM_ROOT/gems/puma-3.12.1/lib/puma/configuration.rb:227 in call
/GEM_ROOT/gems/puma-3.12.1/lib/puma/server.rb:660 in handle_request
/GEM_ROOT/gems/puma-3.12.1/lib/puma/server.rb:474 in process_client
/GEM_ROOT/gems/puma-3.12.1/lib/puma/server.rb:334 in block in run
/GEM_ROOT/gems/puma-3.12.1/lib/puma/thread_pool.rb:135 in block in spawn_thread

Steps to Reproduce

I apologize (!) but whatever I do in development or production, I can’t seem to recreate this issue, but my end users in production can. I need to isolate exactly what they are doing and will post back.

About this issue

Commits related to this issue

Most upvoted comments

I just spent quite some time debugging this. In my case, I was following an auth0 tutorial that instructed to generate a link with <%= button_to "Login", "auth/auth0", method: :post %>. I was banging my head to a wall for a long time because of the InvalidAuthenticityToken exception.

Turns out that the path had to be “/auth/auth0” (slash in the beginning) for rails to correctly compare the path. Shrug. Maybe this helps someone else. Not sure if this is actually a Rails bug… it seems at least little unfriendly.

+1 Having the same issue

@ybakos sorry for the late reply, and thank you for your report.

This gem actually hooks into Rails request stack by adding itself in before_request_phase of OmniAuth’s middleware. If you run rake:middleware, you can see that the request will hit OmniAuth middleware, raise exception, then skipping your application code (where ApplicationController live) entirely.

One way to workaround this issue is to by creating a middleware that rescue ActionController::InvalidAuthenticityToken and redirect user to sign in. Something like:

# app/middlewares/rescue_from_invalid_authenticity_token.rb
class RescueFromInvalidAuthenticityToken
  def call(env)
    yield
  rescue ActionController::InvalidAuthenticityToken
    [302, {'Location' => "/sessions/new, 'Content-Type' => 'text/html'}, ['Invalid Authenticity Token']]
  end
end

Then insert it before OmniAuth::Builder:

config.middlewares.insert_before "RescueFromInvalidAuthenticityToken", OmniAuth::Builder

I have not tested the code above, but I hope I can give you some idea.

Thanks @sikachu, just to add to your code from here https://github.com/cookpad/omniauth-rails_csrf_protection/issues/8#issuecomment-550833651

I’ve tested this code in the wild (Rails 7) and it works:

config.middleware.use RescueFromInvalidAuthenticityToken

class RescueFromInvalidAuthenticityToken
  def initialize app
    @app = app
  end

  def call(env)
     @app.call(env)
  rescue ActionController::InvalidAuthenticityToken
    env["rack.session"]["flash"] = ActionDispatch::Flash::FlashHash.new(alert: "Invalid Authenticity Token.")
    [302, {'Location' => "/sessions/new", 'Content-Type' => 'text/html'}, ['Invalid Authenticity Token']]
  end
end

I had to set Rails.application.config.action_controller.per_form_csrf_tokens = false to get around this issue in apps I’ve upgrade to Rails 6.

@jarthod that is very interesting. I feel like that’s a bug in Rails if upgrading to Rails 6 switch your cache store to :null_store and break session. Would you mind letting me know which guide you followed and which script actually causing that, as we might want to mention that in the README?

I used the classic rake app:update command to upgrade to Rails 6 and that’s the command which changed the cache store (which I saw and approved because I didn’t think it would break anything). This is not necessarily a bug though because by default Rails 6 uses cookie store for sessions AFAIR so it does NOT depend on the cache store, but in my case I changed the session store to CacheStore (because I use this in production) and that’s why removing the cache store broke the sessions for me.

So yeah maybe a little note about that in the Readme could help because the error message is clearly not obvious in this case but it’ll boil down to: “make sure you have a session store that stores stuff” ^^. But people won’t check the gem readme when they get this error I think, they’ll just look it up online and find this issue that’s why I added this comment here 😉 Ideally if we can have a different error message for when the session store is simply returning nil maybe it could make it easier to lead people to the solution.

I followed rails 6 upgrade process which changes config.cache_store = :memory_store to config.cache_store = :null_store by default in dev env and that’s what was causing the issue for me, simply because the CSRF token is stored in the session store (which is using cache_store by default) and the default store in dev is now disabled unless you explicitly enable caching.

@jarthod that is very interesting. I feel like that’s a bug in Rails if upgrading to Rails 6 switch your cache store to :null_store and break session. Would you mind letting me know which guide you followed and which script actually causing that, as we might want to mention that in the README?

maybe this gem could rethink the approach and maybe implement the verification as a before_action instead of as a middleware?

Yep, that won’t work as omniauth is hooking into rails at middleware level so we have to be at this level as well.

I will have some time to working on the failure handling this week, and I’ll cut a new version afterward. I’m going to keep this open for now as I want to hear more about the problem with the cache_store though.

For the record, I recently tried to upgrade my application from rails 5.2.4.2 to rails 6.0.2.2 and I got this error at every form I try to submit, even without oauth if I just submit the login/password devise form ☹️

I’m using:

devise (4.7.1)
omniauth (1.9.1)
omniauth-github (1.4.0)
omniauth-google-oauth2 (0.8.0)
omniauth-oauth2 (1.6.0)
omniauth-rails_csrf_protection (0.1.2)

🕵️‍♂️ But I tried without this gem and saw that I was still getting CSRF errors (less visible though as maybe rescued differently). I followed rails 6 upgrade process which changes config.cache_store = :memory_store to config.cache_store = :null_store by default in dev env and that’s what was causing the issue for me, simply because the CSRF token is stored in the session store (which is using cache_store by default) and the default store in dev is now disabled unless you explicitly enable caching.

So that’s one thing to look for if you end up having this error, I’m not sure why they made this change at it can break every form by default 🤷‍♂️, hopefully it’ll help some people 👍

but in my case I changed the session store to CacheStore (because I use this in production) and that’s why removing the cache store broke the sessions for me.

Got it, thanks!

And yes, I think document that the CSRF token will be stored in session would be useful … so I’ll add that.