omniauth-apple: Can't verify CSRF token authenticity when returning from auth
I am getting 422 Unprocessable Entity errors caused by ActionController::InvalidAuthenticityToken after successful auth redirecting back to the app. I have seen some posts that include the provider_ignores_state: true setting. However, this does not make a difference either (and would potentially cause security issues?).
I am using this as a strategy for Devise, and not directly as Rack middleware, and this is the only auth provider that causes trouble (the other being LinkedIn and Facebook, which work fine).
Is there any reason why this gem would not work as a provider for devise? It seems to be working OK until the redirect step when the CSRF protection causes the problem.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 2
- Comments: 32 (6 by maintainers)
An alternative approach, at least possible on Rails 7, is adding the following to the
config/application.rbfile, placing this inside yourApplicationclass:Ok so… this seems to work:
What are the downsides?
lax. Then when the callback phase is initiated, the cookie won’t come included in the POST request. It’s an edge case, but could happen. And given Apple’s rules about only sending the user’s name and email on the very first attempt, this could be problematic.SameSite=Noneappropriately. In order for this “hack” to be more fail-proof, we should determine if setting it to:noneornil(to let the browser use its default behavior) according to the exceptions outlined in that article’s pseudocode. Luckily, rails_same_site_cookie gem seems to have that code almost ready to use. But still feels hacky.I decided to take a look at what are other Rails-based websites doing with their session cookies and these are my findings:
SameSite=Lax. But, for now, chrome still includes these cookies on POST requests.SameSite=Laxexplicitly.I’m still unsure what we’ll do. Most probably we’ll avoid setting SameSite explicitly for now… but at some point explicit Lax seems to be the best choice.
In any case, I do consider omniauth should potentially try to handle this stuff off-the-shelf. This is certainly NOT Rails-specific. I think the best option could be for omniauth to make itself independent from the session and use a specific cookie of its own to store its intermediate state. This cookie would need to have
SameSite=None, but also have into account the browsers that don’t handle that well.Thank you for your pointers @btalbot! Thanks for hearing @BobbyMcWho!
@btalbot Hi there. FTR, I work with @yjukaku who already posted some comments in this thread last year.
I’ve been taking a 2nd look into this problem because Rails 6.1 has a new default config for
cookies_same_site_protection = :laxand that broke Apple Sign In for us. I think this means many people will be googling for this problem as more teams start adopting that new default.As mentioned before, Chrome has been using
laxas a default for a while already, but that was not breaking Apple Sign In because their default version oflaxis “special” and still sends cookies on POST requests as explained in “What is the Lax + POST mitigation?” but only if it was set in the last 2 minutes:This seems to indicate that the only way to get the Rails’ session cookie when Apple makes their POST request is to configure the
CookieStorewithsame_site: :noneandforce_ssl = truewhich in turn makes the cookies secure too. But even that is not perfect because many browsers are known to be incompatible withSameSite=none. 😞But wouldn’t that also make the session cookie unusable in http://localhost? This would force all developers to setup an SSL certificate to develop locally and be able to use HTTPS if they want to use a session at all, even regardless of omniauth.
There should be some other way…
Doing a bit more digging, I noticed the
CSRF detectederror I’m getting comes from this line in omniauth-oauth2. The problem there is thatsession.delete("omniauth.state"))is not able to read theomniauth.statefrom the session because the session cookie wasn’t part of the POST request.I can think of 2 possible workarounds and I would like your feedback:
omniauth-applebut not other identity providers (because they do GETs), implement a workaround in this gem that would store theomniauth.statein a separate, specific cookie withSameSite=nonebefore taking the user to Apple’s servers. Then in the callback phase that cookie would be available and the normal flow would continue. The downside is that the regular session would still not be valid so any kind of detection of the user being already logged in would not work. Not sure how to solve that.Thoughts?
Given this could potentially require a change in omniauth-oauth2, I think it’d be good if we could get an opinion from @BobbyMcWho.
Finally found a good reference for what is going on. Closing this issue since the problem is more general to omniauth. However, given that Apple
POSTs by default it might be worth mentioning in the README or examples.I fixed this with the following codes:
In the Omniauth callbacks controller:
and in application.rb:
@nhosoya i noticed
skip_forgery_protectionfor the callback in omniauth-apple-sample, which changes the error to the following in my case:ERROR -- omniauth: (apple) Authentication failure! csrf_detected: OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF detectedthis worked for me
As mentioned above if there is another request it will set session cookie back to
:lax, this is a fix to keep it for 2 minutes in Rails 7Hey, thanks for this. I made a small adjustment to verify that the Apple callback path is being used under these conditions. My app seems to get a lot of ‘volunteer’ penetration testers so if someone could exploit my Discord callback being open (my other strategy), I’m sure they would. Heh
@oboxodo Thanks so much for your very helpful notes! Also, thanks so much @dlin-me for doing the legwork to create this utility that’s based on the Chromium incompatible browsers list.
For anyone who finds this, we decided to make the session cookie
SameSite=None; Secureand just hide Apple OAuth for those older incompatible browsers.One thing to note is that if you’re setting
previous_before_request_phasein the initializer, it’ll be static for every user as whatever it was at app startupThanks @btalbot for your answer. So just to clarify, the issue is that we are setting our CSRF token in our session cookie, and that cookie won’t be sent to our server when Apple makes a POST request to our site? And the reason is that our session cookie is likely set to use a
Same-Sitevalue ofLax, which prevents browsers from sending cookies for cross site requests?The callback POST request is made by apple client using Origin and Referer using appleid.apple.com domains. This means that any cookies being used (for sessions, state, etc) will only be passed to the callback method if the cookie Same-Site=None. The default Same-Site is typically Lax now with modern browsers. If you don’t get the session you expect in the callback phase, that is likely the issue.