OIDC csrf_detected can mask user cancel / consent rejection - docs could clarify log inspection

Continuing the discussion from OIDC login via Discourse iOS app occasionally fails with csrf_detected on callback:

Hi all,

This is a follow-up observation linked to my earlier thread about OIDC login failures initiated from iOS in-app browsers. That discussion focused on deterministic csrf_detected failures caused by WKWebView cookie isolation, which now seem well understood and expected.

This linked topic is about operator clarity, not a bug.


Observation

While investigating a series of OIDC login failures, I’ve noticed that the same surface error in Discourse:

/auth/oidc/callback → /auth/failure?message=csrf_detected

can correspond to multiple, fundamentally different upstream causes, depending on what the IdP returned.

From the application UI alone, these cases are indistinguishable. The difference is only visible by inspecting Admin → Logs → env / params.


Examples seen in practice (Azure / Entra ID)

In addition to in-app browser cookie loss, I’ve observed callbacks where Entra ID explicitly returns structured errors such as:

User declined consent

error=consent_required
error_description=AADSTS65004: User declined to consent to access the app

User cancelled login

error=access_denied
error_subcode=cancel

In both cases:
• Azure successfully identified the user
• The user explicitly chose not to proceed (reject / cancel)
• Discourse receives the callback
• The flow ultimately resolves to /auth/failure?message=csrf_detected

From Discourse’s perspective, this is correct and secure behaviour - the state cannot be validated or completed - but the underlying reason is very different from a missing session cookie.


Why this matters for operators

Without checking the log env/params, an admin seeing repeated csrf_detected failures may reasonably assume:
• broken cookies
• SameSite misconfiguration
• mobile browser issues
• IdP instability

…when in reality, some of those failures are simply users choosing not to consent or cancelling the Microsoft prompt.

This distinction only becomes clear if you already know to inspect the raw log payload.


Suggestion (documentation / UX only)

I’m not suggesting any behavioural change to OmniAuth or CSRF handling.

It might be helpful if documentation or troubleshooting guidance explicitly noted that:
• csrf_detected can be the final error for multiple upstream IdP outcomes
• including explicit user actions like cancel or consent rejection
• and that admins should inspect Admin → Logs → env / params to distinguish these cases

This would make it easier for operators to:
• correctly diagnose login failures
• avoid unnecessary configuration changes
• and give users accurate guidance (“you cancelled / declined consent” vs “your browser blocked cookies”).


Context

For clarity: this is separate from the confirmed iOS in-app browser issue discussed in the linked topic. In that case, the IdP never reaches the point of user consent, whereas here the IdP explicitly reports user intent.

Both end up looking similar at the UI level unless logs are examined.


Thanks for reading - posting this mainly as a documentation clarity/data-point for others running OIDC in student-heavy environments where these cases occur frequently.

Happy to provide anonymised examples if useful.