Suite à des investigations supplémentaires et à une confirmation d’utilisation réelle.
Après avoir creusé un peu plus, je pense qu’il s’agit bien d’une limitation du navigateur intégré à iOS (WKWebView) plutôt que de quelque chose de spécifique à Discourse ou à la configuration Azure.
Ce que j’ai pu confirmer
D’après les journaux nginx + Rails et les tests utilisateurs :
- Le flux OAuth est parfois initié à l’intérieur d’un navigateur intégré à iOS (par exemple, Snapchat)
- La connexion Microsoft (login.microsoftonline.com) est chargée à l’intérieur de ce navigateur intégré
- Le rappel OIDC atteint Discourse avec succès
- Mais le cookie de session contenant la valeur d’état ne survit pas
- Ce qui résulte en un :
GET /auth/failure?message=csrf_detected&strategy=oidc
déterministe.
Cela se produit même si le référent est Microsoft et que l’URI de redirection est correcte.
Un UA représentatif :
Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X)
Snapchat/13.76.1.0 (like Safari/..., panda)
Donc, bien que l’interface utilisateur “oneboxe” joliment Microsoft, le navigateur sous-jacent reste le WKWebView de Snapchat, et non Safari / ASWebAuthenticationSession.
L’interception par composant de thème ne fonctionne pas
J’ai tenté d’atténuer cela côté client en utilisant un composant de thème :
- détecter les UA de navigateurs intégrés connus
- bloquer les liens /auth/oidc
- afficher une superposition persistante demandant aux utilisateurs d’ouvrir dans Safari/Chrome
Cependant, cela n’intercepte pas le flux de manière fiable, car :
- la redirection OAuth est initiée côté serveur
- le cookie d’état doit déjà exister avant que le JS client ne s’exécute
- au moment où le thème se charge, les dégâts sont déjà faits
Donc, un composant de thème ne peut pas empêcher de manière fiable ce mode d’échec.
Ce qui fonctionne de manière fiable
La seule atténuation que j’ai trouvée qui fonctionne de manière cohérente est de remplacer le texte du site :
login.omniauth_error.csrf_detected
pour expliquer explicitement que :
- la connexion a échoué à cause d’un navigateur intégré
- les utilisateurs doivent ouvrir le site dans Safari ou Chrome
- puis réessayer la connexion
Ce message est rendu côté serveur après l’échec et apparaît donc même dans les contextes de navigateurs intégrés défectueux.
Cela a considérablement réduit la confusion des utilisateurs, car auparavant, les utilisateurs étaient silencieusement renvoyés à la page de connexion et ne réalisaient souvent pas que quelque chose n’allait pas.
À propos des cookies SameSite
Je n’ai pas changé same_site_cookies à “None”.
Étant donné qu’il s’agit d’un problème d’isolation WKWebView (plutôt qu’une navigation inter-sites dans Safari), la modification de SameSite ne semble pas aborder la cause profonde et pourrait introduire des compromis de sécurité inutiles.
Question ouverte
Compte tenu de ce qui précède, je voulais vérifier si :
- ce comportement est considéré comme attendu lorsque l’OAuth est initié à partir de navigateurs intégrés iOS
- et si Discourse a l’intention de prendre en charge ce flux, ou simplement de documenter que la connexion doit se faire à partir d’un vrai navigateur
Il pourrait également être utile pour Discourse de faire apparaître un message par défaut plus explicite pour csrf_detected dans les contextes OmniAuth, car ce mode d’échec semble de plus en plus courant avec les utilisateurs étudiants arrivant via des liens Snapchat / Instagram.
Je suis heureux de fournir plus de journaux anonymisés si cela est utile - mais à ce stade, le comportement semble très cohérent et reproductible.
Merci d’avoir examiné cela.