Tras una investigación adicional y confirmación del uso en el mundo real.
Tras investigar más a fondo, creo que esto es, de hecho, una limitación del navegador integrado de iOS (WKWebView) en lugar de algo específico de Discourse o de la configuración de Azure.
Lo que he podido confirmar
A partir de los registros de nginx + Rails y las pruebas de usuario:
- El flujo de OAuth a veces se inicia dentro de un navegador integrado de iOS (por ejemplo, Snapchat)
- El inicio de sesión de Microsoft (login.microsoftonline.com) se carga dentro de ese navegador integrado
- La devolución de llamada OIDC llega a Discourse con éxito
- Pero la cookie de sesión que contiene el valor de estado no sobrevive
- Lo que resulta en un:
GET /auth/failure?message=csrf_detected&strategy=oidc
determinista
Esto ocurre a pesar de que el referente es Microsoft y la URI de redirección es correcta.
Un UA representativo:
Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X)
Snapchat/13.76.1.0 (like Safari/..., panda)
Así que, aunque la interfaz de usuario “uniboxea” bien a Microsoft, el navegador subyacente sigue siendo el WKWebView de Snapchat, no Safari / ASWebAuthenticationSession.
La intercepción de componentes de tema no funciona
Intenté mitigar esto del lado del cliente usando un componente de tema:
- detectando UAs de navegadores integrados conocidos
- bloqueando enlaces /auth/oidc
- mostrando una superposición persistente que instruye a los usuarios a abrir en Safari/Chrome
Sin embargo, esto no intercepta el flujo de manera confiable, porque:
- la redirección de OAuth se inicia del lado del servidor
- la cookie de estado debe existir antes de que se ejecute el JS del cliente
- cuando el tema se carga, el daño ya está hecho
Por lo tanto, un componente de tema no puede prevenir de manera confiable este modo de fallo.
Lo que funciona de manera confiable
La única mitigación que he encontrado que funciona consistentemente es anular el texto del sitio:
login.omniauth_error.csrf_detected
para explicar explícitamente que:
- el inicio de sesión falló debido a un navegador integrado
- los usuarios deben abrir el sitio en Safari o Chrome
- y luego reintentar el inicio de sesión
Este mensaje se renderiza del lado del servidor después del fallo y, por lo tanto, aparece incluso en contextos de navegador integrado rotos.
Eso ha reducido significativamente la confusión del usuario, ya que anteriormente los usuarios eran devueltos silenciosamente a la página de inicio de sesión y, a menudo, no se daban cuenta de que algo había salido mal.
Acerca de las cookies SameSite
No he cambiado same_site_cookies a “None”.
Dado que este es un problema de aislamiento de WKWebView (en lugar de navegación entre sitios en Safari), cambiar SameSite no parece abordar la causa raíz y puede introducir compensaciones de seguridad innecesarias.
Pregunta abierta
Dado lo anterior, quería verificar si:
- este comportamiento se considera esperado cuando OAuth se inicia desde navegadores integrados de iOS
- y si Discourse tiene la intención de admitir ese flujo, o simplemente documentar que el inicio de sesión debe realizarse desde un navegador real
También podría ser útil para Discourse mostrar un mensaje predeterminado más explícito para csrf_detected en contextos de OmniAuth, ya que este modo de fallo parece ser cada vez más común con los usuarios estudiantes que llegan a través de enlaces de Snapchat / Instagram.
Estoy dispuesto a proporcionar más registros anonimizados si son útiles, pero en este momento el comportamiento parece muy consistente y reproducible.
Gracias por echarle un vistazo.