Following up with some additional investigation and confirmation from real-world usage.
After digging further, I think this is indeed an iOS in-app browser (WKWebView) limitation rather than anything specific to Discourse or Azure configuration.
What I’ve been able to confirm
From nginx + Rails logs and user testing:
• The OAuth flow is sometimes initiated inside an iOS in-app browser (e.g. Snapchat)
• Microsoft login (login.microsoftonline.com) is loaded inside that in-app browser
• The OIDC callback does reach Discourse successfully
• But the session cookie containing the state value does not survive
• Resulting in a deterministic:
GET /auth/failure?message=csrf_detected&strategy=oidc
This occurs even though the referer is Microsoft and the redirect URI is correct.
A representative UA:
Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X)
Snapchat/13.76.1.0 (like Safari/..., panda)
So although the UI “oneboxes” Microsoft nicely, the underlying browser is still Snapchat’s WKWebView, not Safari / ASWebAuthenticationSession.
Theme component interception does not work
I attempted to mitigate this client-side using a theme component:
• detecting known in-app browser UAs
• blocking /auth/oidc links
• displaying a persistent overlay instructing users to open in Safari/Chrome
However, this does not reliably intercept the flow, because:
• the OAuth redirect is initiated server-side
• the state cookie must already exist before client JS runs
• by the time the theme loads, the damage is already done
So a theme component cannot reliably prevent this failure mode.
What does work reliably
The only mitigation I’ve found that works consistently is overriding the site text:
login.omniauth_error.csrf_detected
to explicitly explain that:
• the login failed due to an in-app browser
• users should open the site in Safari or Chrome
• then retry login
This message is rendered server-side after the failure and therefore appears even in broken in-app browser contexts.
That has significantly reduced user confusion, since previously users were silently returned to the login page and often didn’t realise anything went wrong.
About SameSite cookies
I have not switched same_site_cookies to “None”.
Given this is a WKWebView isolation issue (rather than cross-site navigation in Safari), changing SameSite doesn’t appear to address the root cause and may introduce unnecessary security trade-offs.
Open question
Given the above, I wanted to sanity-check whether:
• this behaviour is considered expected when OAuth is initiated from iOS in-app browsers
• and whether Discourse intends to support that flow, or simply document that login must occur from a real browser
It might also be useful for Discourse to surface a more explicit default message for csrf_detected in OmniAuth contexts, as this failure mode seems increasingly common with student users arriving via Snapchat / Instagram links.
Happy to provide more anonymised logs if useful - but at this point the behaviour seems very consistent and reproducible.
Thanks for taking a look.