OAuth2 csrf_detected with Discord 'Connect' functionality

Hello! I’m in desperate need of some help with figuring out this error:


Steps to reproduce:
When a user clicks on their Discord “Connect” button in Preferences,
image
it correctly redirects the user to Discord’s authorization page.


However after clicking the Authorize button, the user is redirected and met with this message on our forum:
image
and the error at the top of this topic shows up in the admin logs.


I feel like I’ve read and tried everything to fix this, but it continues to happen. I’ve made sure the Discord Client ID and Secret site settings are correct.
I also made sure the URI was the correct syntax (based on a few related topics I’ve seen):

Any suggestions? I’m willing to try anything, even if you’re unsure it will work :laughing:

Any ideas? Still struggling with this :confused:

I think(?) I’ve narrowed it down to an nginx and/or caching issue? Is there supposed to be auth-specific or CSRF-specific stuff defined in discourse.conf that we could be missing?

@merefield, @david, @sam - sorry for the pings but I see your names in a lot of the older csrf related discussions in the past. Do you have any recommendations for this? With Discord auth being an integrated part of discourse, I’m stumped what could be causing this.

I appreciate any and all help in advance, thanks :smiling_face:

We must be breaking one of these?

I’m still unable to find a pattern. Sometimes it works and connects me correctly, but other times I’m met with the csrf page.

At the moment, I’m most suspect of the last condition check in verified_request?.
Are there any ways to easily check if (valid_request_origin? && any_authenticity_token_valid?) is returning true?

I apologize for the lack of any debug-able information, but I think I was (painfully) able to find (at least what I think) is the issue. I’m still not sure what the fix is, so please read on :kissing_heart:


The pictures below show a back-to-back instance where I was able to successfully link my account, refreshed/tried it again, and unsuccessfully hit the csrf detected page. I was in an incognito window and did/changed literally nothing between the successful connect and csrf failure. Here’s what I found:

So this first pic shows the _forum_session cookie matching in both 1 and 2 request headers, which resulted in a successful connect.

However after I reloaded the page and tried again (and failed to connect), you can see my search on the left side only shows 1 occurrence of the _forum_session cookie in a request header when it resulted in a failure.

tl;dr: I’m pretty sure the issue stems from the forum_session cookie in the discord?reconnect request header and then the following callback? request header not matching. What would cause them to be different?

Ok, I think we’re getting closer.

So in this pic below, you can see an update POST request happening directly after the discord?reconnect POST request.


And sure enough, it’s setting the _forum_session cookie which is causing it to mismatch like I described above.

If I check a successful connection instance (below), you can see the update only occurs before the discord?reconnect POST request.

This causes the _forum_session cookie to match and for it to successfully connect the account without the csrf issue.

How do I prevent that update from occurring after the user has begun the connection process?

@FerrariFlunker sorry for slow response, but haven’t had chance to look at this, it would be great if the core team could.

if its any consolation, I can repro, I believe, I’m getting the same error:

(discord) Authentication failure! csrf_detected: OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF detected

No worries @merefield, I appreciate the response!

Are you getting that error in your own environment? I think either way, this error needs to be looked at by the core team. I’m 2+ weeks into debugging this and still haven’t found an answer :confused:

Nice digging! This kind of race condition could certainly cause the issues you’re seeing.

That said, we haven’t had any other reports of this problem, so it sounds like it must be something specific to your site/configuration. What plugins do you have installed on the site? Can you open up the “update” call and see what payload is being sent?

3 Likes

After taking a look at the update calls, I’m thinking you’re right. Here are some screenshots of confirmed ‘update’ requests that caused csrf failures.

csrf1

csrf2

I’m seeing a pattern involving cdn :face_with_monocle: What would be misconfigured with that? Let me know if you still need a list of our plugins, I just figured I’d save this reply from +1 picture :smile:

1 Like

I have an exciting update! A few days ago, I took a closer look at the ‘update’ payload and happened to successfully associate this:

with one of our plugins! :partying_face: :expressionless: :smile:
After disabling the plugin, further testing, and a successful launch of the related feature to the community… I think I can confidently say we found the culprit.

So this plugin turned out to be the trouble-maker: GitHub - discourse/discourse-chat: Chat inside Discourse

In hindsight it makes sense why this would be the culprit- the plugin is still marked as experimental and isn’t intended for production sites. :sweat:

Since I’m 3+ weeks into diagnosing this issue and need to get back on track with our community’s other projects :face_with_spiral_eyes:, unfortunately I won’t be able to help find the fix for the discourse-chat plugin.

If someone ends up pushing a fix to the plugin, we’d (most likely :smile:) look into re-enabling the plugin on our site, but for now we need stable associated account Connect functionality

Thanks again to everyone who helped diagnose! :+1:

1 Like

Thanks for the super detailed investigation @FerrariFlunker!

I’ve just made a PR for a fix in Discourse core:

The reason it was fixed after removing the chat plugin is that Chat makes heavy use of this ‘PresenceChannel’ API, and so the issue is far more likely to happen. I don’t think any changes will be required in chat.

4 Likes

Should this fix the same issue with Google Logins? My users on one of my instances where we were testing the chat plugin loved it, but it broke Google logins with the same error as the Discord logins.

3 Likes

Yep, this fix will apply to all different login types :+1:

3 Likes