OIDC csrf_detected может маскировать отмену пользователем или отказ в согласии — документацию можно уточнить для проверки логов

Продолжение обсуждения из темы OIDC-вход через приложение Discourse для iOS иногда завершается ошибкой csrf_detected на обратном вызове:

Всем привет,

Это дополнительное наблюдение, связанное с моей предыдущей темой о сбоях входа через OIDC, инициированных из встроенных браузеров на iOS. В том обсуждении рассматривались детерминированные сбои с ошибкой csrf_detected, вызванные изоляцией cookie в WKWebView, которые теперь, кажется, хорошо поняты и ожидаемы.

Эта связанная тема касается ясности для администраторов, а не ошибки.


Наблюдение

В ходе расследования серии сбоев входа через OIDC я заметил, что одна и та же поверхностная ошибка в Discourse:

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

может соответствовать нескольким фундаментально разным причинам на стороне провайдера идентификации (IdP), в зависимости от того, что вернул IdP.

Только по интерфейсу приложения эти случаи неразличимы. Разница видна только при проверке раздела Администрирование → Журналы → env / params.


Примеры из практики (Azure / Entra ID)

Помимо потери cookie во встроенных браузерах, я наблюдал обратные вызовы, в которых Entra ID явно возвращает структурированные ошибки, такие как:

Пользователь отказал в согласии

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

Пользователь отменил вход

error=access_denied
error_subcode=cancel

В обоих случаях:
• Azure успешно идентифицировал пользователя
• Пользователь явно решил не продолжать (отказ / отмена)
• Discourse получает обратный вызов
• Поток в конечном итоге завершается ошибкой /auth/failure?message=csrf_detected

С точки зрения Discourse это правильное и безопасное поведение — состояние не может быть проверено или завершено, — но основная причина сильно отличается от отсутствия cookie сессии.


Почему это важно для администраторов

Без проверки параметров env/params в логах администратор, видя повторяющиеся сбои с ошибкой csrf_detected, может разумно предположить:
• проблемы с cookie
• неправильная конфигурация SameSite
• проблемы с мобильными браузерами
• нестабильность IdP

…когда на самом деле некоторые из этих сбоев — просто пользователи, которые отказались дать согласие или отменили запрос Microsoft.

Это различие становится очевидным только если вы уже знаете, что нужно проверить полезную нагрузку сырых логов.


Предложение (только документация / UX)

Я не предлагаю никаких изменений в поведении OmniAuth или обработки CSRF.

Было бы полезно, если бы в документации или руководстве по устранению неполадок явно указывалось, что:
• csrf_detected может быть конечной ошибкой для нескольких различных результатов работы IdP на стороне провайдера
• включая явные действия пользователя, такие как отмена или отказ в согласии
• и что администраторы должны проверять раздел Администрирование → Журналы → env / params, чтобы различать эти случаи

Это облегчит администраторам:
• правильную диагностику сбоев входа
• избежание ненужных изменений конфигурации
• и предоставление пользователям точных рекомендаций («вы отменили / отказались дать согласие» против «ваш браузер заблокировал cookie»).


Контекст

Для ясности: это отдельная проблема от подтвержденной проблемы со встроенными браузерами iOS, обсуждаемой в связанной теме. В этом случае IdP даже не доходит до этапа согласия пользователя, тогда как здесь IdP явно сообщает о намерениях пользователя.

Оба случая выглядят одинаково на уровне интерфейса, если не просматривать логи.


Спасибо за прочтение. Публикую это в основном как уточнение документации и пример данных для тех, кто использует OIDC в средах с большим количеством студентов, где такие случаи происходят часто.

Готов предоставить анонимизированные примеры, если это будет полезно.

Небольшая особенность просмотра логов (к сведению)

Один мелкий нюанс, который меня запутал при анализе логов:

В разделе Администрирование → Логи → env → params значения могут отображаться с экранированными последовательностями, например:

\u0026

Это просто экранирование JSON/Ruby для символа &. Например:

error=access_denied\u0026error_subcode=cancel

следует читать как:

error=access_denied&error_subcode=cancel

Ничего не искажается и не теряется — это исключительно особенность отображения/сериализации, но полезно знать об этом, чтобы операторы не упустили сигнал от IdP при диагностике сбоев csrf_detected.