Ethsim2
(Ethan )
2026 年1 月 31 日 11:51
1
您好,
我正在运行 Discourse (2026.2.0-latest (f7cec86997 )) 并使用 OpenID Connect(Azure / Entra ID 作为 IdP)。
我注意到偶尔会出现登录失败的情况,但这似乎只发生在用户尝试通过 Discourse iOS 应用程序登录时。
从服务器日志来看,流程如下:
POST /auth/oidc
GET /auth/oidc/callback?...state=...
(oidc) Authentication failure! csrf_detected
回调确实到达了 Discourse,但 CSRF/state 验证失败,因此没有创建用户帐户。
周围的日志表明这发生在应用程序交接流程中:
application_name=Discourse - iPhone
auth_redirect=discourse://auth_redirect
从用户的角度来看,没有明显的迹象——他们只是被返回到登录屏幕,而且通常不记得看到过错误。
通过 Safari 或桌面浏览器登录时似乎不会发生这种情况。
我的假设是这与 iOS 的 Cookie 分区/应用内浏览器和应用回调之间的上下文切换有关。
我只是想确认一下:
这是 OIDC + iOS 应用程序的预期行为吗?
除了确保严格的规范 HTTPS 源之外,还有没有推荐的缓解措施?
谢谢——如果需要,我很乐意提供匿名化的日志片段。
Ethsim2
(Ethan )
2026 年2 月 1 日 09:31
2
来自 nginx 访问日志的额外数据点:
一个有代表性的失败(2026-01-25 11:44:10 UTC)显示 OIDC 回调请求来自 iOS 应用内浏览器 的用户代理(Snapchat),而不是 Discourse iOS 应用的 webview 用户代理:
GET /auth/oidc/callback?...state=... 302
UA: Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) ... Snapchat/13.76.1.0 (like Safari/..., panda)
Referer: https://login.microsoftonline.com/
紧接着是:
GET /auth/failure?message=csrf_detected&strategy=oidc
因此,看起来 OAuth 流程有时会在 iOS 应用内浏览器(Snapchat/其他)中启动,
然后发生跳转(我也在日志中看到包含 auth_redirect=discourse://auth_redirect 的记录),
并且会话 cookie/状态不能持续保持一致。
当前设置:SiteSetting.same_site_cookies = "Lax"。
问题:当登录从 iOS 应用内浏览器启动并随后深度链接到 Discourse 应用时,Discourse 移动应用的身份验证流程是否可以保证可靠?
将 same_site_cookies 切换到“None”是推荐的缓解措施,还是有更好的方法?
Ethsim2
(Ethan )
2026 年2 月 1 日 11:22
3
在进行一些额外的调查并从实际使用中确认后,我有了以下发现。
深入研究后,我认为这确实是 iOS 应用内浏览器 (WKWebView) 的限制,而不是 Discourse 或 Azure 配置的特定问题。
我确认了以下几点
根据 nginx + Rails 日志和用户测试:
OAuth 流程有时在 iOS 应用内浏览器(例如 Snapchat)中启动
Microsoft 登录 (login.microsoftonline.com ) 在该应用内浏览器中加载
OIDC 回调成功到达 Discourse
但包含状态值的会话 cookie 无法保留
导致确定性的结果:
GET /auth/failure?message=csrf_detected&strategy=oidc
即使引用者是 Microsoft 且重定向 URI 正确,也会发生这种情况。
一个代表性的用户代理 (UA):
Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X)
Snapchat/13.76.1.0 (like Safari/..., panda)
因此,尽管用户界面很好地“一框显示”了 Microsoft,但底层浏览器仍然是 Snapchat 的 WKWebView,而不是 Safari / ASWebAuthenticationSession。
主题组件拦截不起作用
我尝试使用主题组件在客户端缓解此问题:
检测已知的应用内浏览器 UA
阻止 /auth/oidc 链接
显示持久性覆盖层,指示用户在 Safari/Chrome 中打开
然而,这无法可靠地拦截该流程,因为:
OAuth 重定向是服务器端启动的
状态 cookie 必须在客户端 JavaScript 运行之前就已存在
当主题加载时,损害已经造成
因此,主题组件无法可靠地防止这种失败模式。
哪些有效且可靠
我发现唯一一致有效的方法是覆盖站点文本:
login.omniauth_error.csrf_detected
明确解释:
登录因应用内浏览器而失败
用户应在 Safari 或 Chrome 中打开网站
然后重试登录
此消息在失败后由服务器端渲染,因此即使在出现问题的应用内浏览器环境中也会显示。
这大大减少了用户的困惑,因为以前用户会被静默地返回登录页面,而且通常没有意识到出了什么问题。
关于 SameSite cookie
我没有将 same_site_cookies 更改为“None”。
鉴于这是一个 WKWebView 隔离问题(而不是 Safari 中的跨站导航),更改 SameSite 似乎无法解决根本原因,并可能带来不必要的安全权衡。
待解决的问题
鉴于以上情况,我想确认一下:
当 OAuth 从 iOS 应用内浏览器启动时,这种行为是否被认为是预期的
Discourse 是否打算支持该流程,或者只是记录登录必须从真正的浏览器中进行
对于 Discourse 来说,可能还有用的是在 OmniAuth 上下文中显示一个更明确的默认消息,因为随着越来越多的学生用户通过 Snapchat / Instagram 链接访问,这种失败模式似乎越来越常见。
如果需要,我很乐意提供更多匿名日志——但此时行为似乎非常一致且可重现。
感谢您的关注。
Ethsim2
(Ethan )
2026 年2 月 1 日 11:55
4
另一个可能有助于确认这是 WKWebView 的确定性限制而非间歇性行为的数据点是:
通过关联多个失败案例中的 nginx 访问日志,我发现应用内浏览器在用户重试登录时会重复使用相同的 OIDC 状态值。
示例(已清理):
• 观察到相同的状态哈希 14 次
• 所有请求来自:
Snapchat/13.77.0.51 (like Safari…, panda)
• 分布在大约 1 小时的重复登录尝试中
• 每次尝试的结果是:
/auth/oidc/callback → /auth/failure?message=csrf_detected
这有力地表明:
• 浏览器从未成功存储包含原始状态的会话 cookie
• 每次重试都会重用相同的陈旧状态参数
• 因此,Discourse 每次都会正确拒绝回调
相比之下,当同一用户在 Safari 中打开链接时,会生成一个新状态,登录会立即成功。
因此,这似乎是某些 iOS 应用内浏览器中完全确定的失败模式,而不是时序或竞态条件。
从 Discourse 的角度来看,CSRF 保护的行为完全符合预期——浏览器环境根本无法维持所需的会话连续性。
我认为这进一步支持了:
• 这不是主题组件或客户端 JavaScript 可以缓解的问题
• 唯一可靠的处理方式是服务器端消息传递和文档记录
发帖以防此数据点有助于确认预期行为。