Продолжаю с дополнительным расследованием и подтверждением на основе реального использования.
После более глубокого анализа я считаю, что это действительно ограничение встроенного браузера iOS (WKWebView), а не что-то специфичное для конфигурации Discourse или Azure.
Что мне удалось подтвердить
На основе логов nginx + Rails и тестирования пользователями:
• Поток OAuth иногда инициируется внутри встроенного браузера iOS (например, Snapchat)
• Вход в Microsoft (login.microsoftonline.com) загружается внутри этого встроенного браузера
• OIDC-колбэк успешно достигает Discourse
• Но куки сессии, содержащая значение state, не сохраняется
• В результате возникает детерминированная ошибка:
GET /auth/failure?message=csrf_detected&strategy=oidc
Это происходит даже несмотря на то, что referer указывает на Microsoft, а URI перенаправления корректен.
Примерный User-Agent:
Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X)
Snapchat/13.76.1.0 (like Safari/..., panda)
Таким образом, хотя интерфейс «подсказывает» Microsoft, фактический браузер — это всё ещё WKWebView Snapchat, а не Safari или ASWebAuthenticationSession.
Перехват через компонент темы не работает
Я попытался смягчить проблему на стороне клиента с помощью компонента темы:
• обнаружение известных User-Agent встроенных браузеров
• блокировка ссылок /auth/oidc
• отображение постоянного оверлея с инструкцией открыть сайт в Safari/Chrome
Однако это не позволяет надёжно перехватить поток, потому что:
• перенаправление OAuth инициируется на стороне сервера
• cookie state должен уже существовать до запуска клиентского JS
• к моменту загрузки темы проблема уже возникает
Поэтому компонент темы не может надёжно предотвратить этот сбой.
Что работает надёжно
Единственное решение, которое работает стабильно, — это переопределение текста сайта:
login.omniauth_error.csrf_detected
чтобы явно объяснить, что:
• вход не удался из-за использования встроенного браузера
• пользователям следует открыть сайт в Safari или Chrome
• а затем повторить вход
Это сообщение отображается на стороне сервера после сбоя и поэтому появляется даже в сломанных контекстах встроенных браузеров.
Это значительно снизило путаницу среди пользователей, поскольку ранее они молча возвращались на страницу входа и часто не осознавали, что что-то пошло не так.
О куки SameSite
Я не переключал параметр same_site_cookies на значение “None”.
Учитывая, что это проблема изоляции WKWebView (а не межсайтовая навигация в Safari), изменение SameSite не устраняет корневую причину и может привести к ненужным компромиссам в области безопасности.
Открытый вопрос
Учитывая вышесказанное, я хотел бы уточнить:
• является ли такое поведение ожидаемым при инициации OAuth из встроенных браузеров iOS
• и планирует ли Discourse поддерживать такой поток или просто документировать, что вход должен выполняться из полноценного браузера
Также было бы полезно, если бы Discourse отображал более явное сообщение по умолчанию для csrf_detected в контекстах OmniAuth, поскольку этот тип сбоя становится всё более распространённым среди студентов, переходящих по ссылкам из Snapchat или Instagram.
Готов предоставить дополнительные анонимизированные логи, если это потребуется — но на данный момент поведение кажется очень последовательным и воспроизводимым.
Спасибо, что уделили время рассмотрению.