Подключить MFA к современному веб-приложению обычно несложно: достаточно подключить SAML или OIDC на стороне самого приложения и включить второй фактор на Identity Provider. Проблемы начинаются там, где сервис не умеет ни в SAML, ни в OIDC, а переписывать его рискованно, дорого или попросту некому.

Во многих корпоративных сетях до сих пор живут монолитные legacy-системы, которые лучше не трогать, и кастомные сервисы, давно оставшиеся без активного развития. На такой случай придумана концепция предаутентификации. Она позволяет вынести всю сложную логику проверки прав, работу с токенами и криптографией на внешний контур. По сути, перед приложением устанавливается барьер, который отсекает нелегитимные запросы еще до того, как они дойдут до бэкенда.

В этой статье системный инженер Артур Газеев и я, Аскар Добряков, ведущий эксперт направления защиты данных и приложений в К2 Кибербезопасность разбираем, как вынести MFA на периметр для legacy-системы, которую нельзя быстро переписать. Покажем архитектуру решения, объясним, почему выбрали связку Nginx + OAuth2 Proxy + Indeed AM, и разберем, на каких настройках поднимали и отлаживали эту схему.

Что такое предаутентификация, и чем она отличается от нормальной

Суть предаутентификации в том, чтобы вынести логику проверки доступа за пределы приложения и добавить еще один этап аутентификации на уровне реверс-прокси. Если клиент не прошел проверку на периметре, его трафик вообще не должен дойти до защищаемого приложения. Для этого нам потребуются Nginx, прокси-менеджер и провайдер идентификации.

Теоретически эту задачу можно решить сторонним OIDC-модулем для Nginx, но тогда пришлось бы завязываться на нестандартную сборку веб-сервера и брать на себя дополнительные риски сопровождения и атак цепочки поставок. Поэтому, разрабатывая эту схему, мы сознательно оставили Nginx в стандартной сборке, а логику аутентификации вынесли в отдельный компонент — OAuth2 Proxy. Мы смотрели и на альтернативы вроде Gatekeeper, но в нашем сценарии они оказались менее гибкими по интеграции и маршрутизации.

Выбор Identity Provider — отдельная тема. Сперва мы рассматривали Keycloak. У него зрелая поддержка OIDC и SAML, гибкая настройка MFA, большая экосистема и понятная модель кастомизации. Но в нашем случае важны не только функции, но и эксплуатация: SLA, формальная зона ответственности, предсказуемые обновления.

В итоге в этом проекте мы остановились на Indeed AM. Решающими оказались три фактора: готовая встраиваемость во внутренний корпоративный контур, единая точка управления политиками доступа и вендорская поддержка.

Остается Nginx. Сам по себе он не умеет в SSO и ничего не знает об OIDC, зато у него есть штатный модуль auth_request. Он позволяет приостановить основной запрос, выполнить внутренний подзапрос к внешнему сервису и либо пустить клиента дальше, либо отправить его на аутентификацию.

В нашей схеме это работает так:

  1. Клиент стучится на защищаемый URL, а Nginx приостанавливает обработку и отправляет копию заголовков запроса в сторону OAuth2 Proxy.

  2. OAuth2 Proxy изучает данные исходного запроса в поисках куки авторизации, подтверждающей, что у пользователя есть активная сессия.

  3. Сценарий А: если валидная кука найдена, прокси отвечает кодом 200 OK или любым другим статусом из диапазона 2xx. Для Nginx это сигнал, что запрос можно пропустить дальше на бэкенд.

  4. Сценарий Б: если куки нет или ее срок действия истек, OAuth2 Proxy возвращает 401 Unauthorized. Nginx перехватывает этот ответ и превращает его в 302 Found, отправляя браузер пользователя на страницу авторизации Identity Provider.

  5. Сценарий В: если в ходе проверки выясняется, что доступ пользователю запрещен, например из-за нехватки ролей, сервис возвращает 403 Forbidden, а запрос обрывается.

После перенаправления на страницу IdP пользователь вводит свои учетные данные и проходит второй фактор (например, вводит TOTP-код из приложения). Если все верно, IdP редиректит его обратно на callback-адрес OAuth2 Proxy. Прокси выписывает сессионную куку, и цикл повторяется, но теперь подзапрос Nginx возвращает 200 OK.

На практике это означает простую вещь: legacy-приложение остается нетронутым, а контроль доступа переносится на внешний контур. Бэкенд получает уже проверенный запрос с данными о пользователе в заголовках, а управление сессией, редиректами и MFA берут на себя Nginx, OAuth2 Proxy и IdP.

Для корпоративного контура у такой схемы есть еще один плюс: она создает удобную точку наблюдения. Поскольку любой неавторизованный запрос к защищаемому приложению неминуемо заканчивается перенаправлением на Identity Provider, он становится удобной точкой для мониторинга при помощи SIEM.

Шаг 1. Установка и настройка OAuth2 Proxy

Для начала перейдем на Linux-сервер и установим актуальную версию OAuth2 Proxy.
Свежие релизы доступны на GitHub:

wget https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.5.1/oauth2-proxy-v7.5.1.linux-amd64.tar.gz
tar xvzf oauth2-proxy-v7.5.1.linux-amd64.tar.gz
mv oauth2-proxy-v7.5.1.linux-amd64 /opt

Для удобства работы делаем символьную ссылку:

ln -s /opt/oauth2-proxy-v7.5.1.linux-amd64 /opt/oauth2-proxy


Затем создаем сервисного пользователя, группу и выставляем права:

export PATH="$PATH:/sbin:/usr/sbin:usr/local/sbin"
useradd -d /dev/null -s /usr/sbin/nologin oauth2-proxy
groupadd oauth2-proxy
usermod -a -G oauth2-proxy oauth2-proxy

chmod -R 775 /opt/oauth2-proxy
chown -R oauth2-proxy:oauth2-proxy /opt/oauth2-proxy/
chmod 755 /etc/oauth2-proxy.cfg

Вся логика работы этого прокси хранится в файле /etc/oauth2-proxy.cfg.

Ниже — стендовый конфиг с комментариями. В нем намеренно оставлены небезопасные параметры, которые использовались только для отладки: отключенный PKCE, cookie_secure="false", cookie_httponly="false", пропуск проверки сертификатов и отладочные заголовки с данными сессии.

Конфиг с комментариями:

# === Основные параметры ===

skip_provider_button = "true"
# Пропускать кнопку выбора провайдера на странице входа.
# Полезно, если только один провайдер OAuth2.

reverse_proxy="true"
# Режим обратного прокси.
# Включает поддержку заголовков X-Forwarded-*, без которых прокси не поймет, с какого реального IP и по какому протоколу пришел клиент.Это сломает механизм формирования Callback URL.

provider="oidc"
# Тип провайдера -- OpenID Connect (OIDC), современный протокол поверх OAuth2.

provider_display_name="Indeed IdP"
# Отображаемое имя провайдера на странице входа.

skip_oidc_discovery="true"
# Пропускаем автоматическое обнаружение endpoints через .well-known/openid-configuration.
# Все endpoints заданы вручную ниже.

code_challenge_method=""
# Метод PKCE (Proof Key for Code Exchange) -- отключен.
# Обычно "S256" для усиления безопасности.

errors_to_info_log="true"
# Записывать ошибки в info-лог в дополнение к error-логу.

https_address=":4180"
# Адрес и порт, на котором запускается oauth2-proxy.

# === Учетные данные клиента ===

client_id="nginx_oauth"
# Идентификатор клиента -- должен совпадать с ClientId в app-settings.json на стороне IdP.

client_secret="my_top_secret"
# Секрет клиента -- должен совпадать с ClientSecret на стороне IdP.
# ВНИМАНИЕ: В продакшене не стоит оставлять секреты в конфиге в открытом виде. Используйте переменные окружения или менеджеры секретов (HashiCorp Vault).

# === URL эндпоинтов ===

redirect_url="https://oauth2.test.ru:4180/oauth2/callback"
# Callback URL -- сюда IdP вернет пользователя после успешной аутентификации.

login_url="https://idp.test.ru/am/idp/connect/authorize"
# URL для начала процесса авторизации OAuth2.

backend_logout_url="https://oauth2.test.ru:4180/am/idp/connect/logout"
# URL для выхода из системы на стороне провайдера.

profile_url="https://idp.test.ru/am/idp/connect/userinfo"
# URL для получения информации о пользователе (OIDC userinfo endpoint).

redeem_url="https://idp.test.ru/am/idp/connect/token"
# URL для обмена authorization code на access token.

oidc_jwks_url="https://idp.test.ru/am/idp/.well-known/jwks"
# URL для получения JSON Web Key Set (публичные ключи для проверки JWT
oidc_issuer_url="https://idp.test.ru/am/idp"
# URL издателя (issuer) токенов.

# === Заголовки ===

set_xauthrequest="true"
# Устанавливать заголовки X-Auth-Request-* для передачи данных аутентификации.
# Когда аутентификация пройдет, прокси вытащит из JWT информацию о пользователе и положит ее в HTTP-заголовки ответа, чтобы Nginx смог их забрать.

set_authorization_header="true"
# Устанавливать Authorization заголовок с Bearer-токеном.

pass_access_token="true"
# Передавать access token в бэкенд.

pass_authorization_header="true"
# Передавать Authorization заголовок в бэкенд.

pass_user_headers="true"
# Передавать заголовки с информацией о пользователе (X-User, X-Email).

pass_host_header="true"
# Передавать оригинальный Host заголовок.

whitelist_domains=["*.ru", ".test.ru"]
# Белый список доменов для перенаправления после аутентификации.
# Предотвращает атаки open redirect.

insecure_oidc_allow_unverified_email="true"
# Разрешить неверифицированные email-адреса.
# ВНИМАНИЕ: Потенциальный риск безопасности!

scope="openid"
# Запрашиваемый scope. openid -- минимальный для OIDC.
# Можно добавить: profile, email, offline_access и др.

oidc_email_claim="email"
# Название claim в ID-токене, содержащего email.

email_domains=["*"]
# Разрешенные домены email-адресов. ["*"] -- все домены разрешены.

# === Cookie ===

cookie_secret="vNQ9Z1RCjIPvEQC5H0a72JEwP1vEEQ=="
# Секрет для шифрования куки (16, 24 или 32 байта для AES-128/192/256).
# ВНИМАНИЕ: В продакшене не стоит остав секреты в конфиге в открытом виде. Используйте переменные окружения или менеджеры секретов (HashiCorp Vault).

cookie_secure="false"
# Передавать куки только по HTTPS. false -- разрешает HTTP (только для стенда!).

cookie_httponly="false"
# Запретить доступ к куки через JavaScript. false -- позволяет доступ (риск XSS!).

cookie_name="_oauth2_cookie"
# Имя сессионной куки.

cookie_samesite="lax"
# SameSite-атрибут для базовой защиты от CSRF.

cookie_csrf_per_request="true"
# Генерировать новый CSRF-токен для каждого запроса.

cookie_csrf_expire="15m0s"
# Время жизни CSRF-токена (15 минут).

cookie_expire="8h0m0s"
# Время жизни сессионной куки (8 часов).

cookie_refresh="1h0m0s"
# Интервал обновления куки (1 час).

cookie_domains=".prod.rus"
# Домен, для которого устанавливается куки.
# Точка в начале -- куки будет доступна для всех поддоменов.

# === SSL/TLS ===

force_https="true"
# Принудительное перенаправление HTTP → HTTPS.

ssl_upstream_insecure_skip_verify="true"
# Не проверять SSL-сертификаты бэкенд-серверов.
# ВНИМАНИЕ: Опасная настройка для продакшена!

ssl_insecure_skip_verify="true"
# Не проверять SSL-сертификаты OAuth2-провайдера.
# ВНИМАНИЕ: Опасная настройка для продакшена!

tls_cert_file="/etc/ssl/private/oauth2.test.ru.crt"
# Путь к SSL-сертификату для oauth2-proxy.

tls_key_file="/etc/ssl/private/oauth2.test.ru.key"
# Путь к приватному ключу SSL.

tls_min_version="TLS1.2"
# Минимальная версия TLS (устаревшие TLS 1.0/1.1 отключены).

# === Логирование ===

logging_filename="/var/log/oauth02/logs.log"
# Путь к файлу логов.

logging_max_size=10
# Максимальный размер файла лога в мегабайтах перед ротацией.

logging_max_backups=5
# Максимальное количество архивных файлов логов.

logging_max_age=30
# Максимальный возраст архива логов в днях.

logging_compress=true
# Сжимать архивные логи.

logging_local_time=true
# Использовать локальное время вместо UTC.

Создадим systemd-юнит для автозапуска демона:

sudo systemctl edit --full --force oauth2-proxy.service

Содержимое юнита:

[Unit]
Description=reverse proxy

[Service]
Type=simple
User=oauth2-proxy
WorkingDirectory=/opt/oauth2-proxy
EnvironmentFile=/etc/oauth2-proxy.cfg
ExecStart=/opt/oauth2-proxy/oauth2-proxy --config=/etc/oauth2-proxy.cfg
Restart=always
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=process

[Install]
WantedBy=multi-user.target

Активируем и запускаем:

systemctl daemon-reload
systemctl enable oauth2-proxy
systemctl start oauth2-proxy

Провайдер настроен, прокси запущен и слушает порт 4180.

Шаг 2. Регистрация клиента в Indeed AM

Итак, провайдер настроен, прокси запущен и слушает порт 4180. Теперь научим IdP доверять OAuth2 Proxy. Для этого развернем Indeed AM в инфраструктуре Windows на базе IIS (Internet Information Services). Первым делом идем на сервер провайдера (idp.test.ru) и открываем конфигурационный файл C:\inetpub\wwwroot\am\idp\app-settings.json.

Здесь мы регистрируем нового клиента с идентификатором nginx_oauth. Прописываем общий секретный ключ (ClientSecret), разрешенные Redirect URIs (куда IdP должен вернуть пользователя после успешного ввода пароля и второго фактора) и запрашиваем список Permissions. Без этого блока настроек IdP просто отвергнет любой запрос от нашего прокси как нелегитимный.

{
 "ClientId": "nginx_oauth",
 "ClientSecret": "my_top_secret",
 "DisplayName": "nginx proxy auth request oauth2",
 "Permissions": [
   "ept:authorization",
   "ept:device",
   "ept:introspection",
   "ept:logout",
   "ept:revocation",
   "ept:token",
   "gt:authorization_code",
   "gt:client_credentials",
   "gt:urn:ietf:params:oauth:grant-type:device_code",
   "gt:implicit",
   "gt:password",
   "gt:refresh_token",
   "rst:code",
   "rst:code id_token",
   "rst:code id_token token",
   "rst:code token",
   "rst:id_token",
   "rst:id_token token",
   "rst:none",
   "rst:token",
   "scp:address",
   "scp:email",
   "scp:phone",
   "scp:profile",
   "scp:roles"
 ],
 "RedirectUris": [
   "https://oauth2.test.ru:4180/oauth2/callback"
 ]
}

Параметры ClientId и ClientSecret должны совпадать с теми, что мы указали в конфиге OAuth2 Proxy. RedirectUris — это адрес, на который IdP вернет ответ через браузер пользователя после успешной аутентификации. Далее, в том же файле app-settings.json, нужно научить провайдера отправлять email-адрес пользователя в ответе. В секции CustomAttributes добавляем:

{
 "ServiceProvider": "nginx_oauth",
 "Attributes": [
   {
     "Name": "email",
     "UserNameFormat": "Email"
   }
 ]
}

Один из важных моментов на стороне IIS — настройка сертификата для работы с протоколом OIDC, который используется для криптографической подписи ответов (JWT-токенов). В секции OIDC → IDP необходимо жестко  задать отпечаток сертификата:

"OIDC": {
 "IDP": {
   "CertificateThumbprint": "d2846fb4d27f1865418501618dbbe51d9e4aae42"
 }
}

Этот сертификат должен лежать в локальном хранилище системы (Local Computer Personal Store). Сохраняем файл, перезапускаем пул IIS, и провайдер готов к приему гостей.

Шаг 3. Настройка Nginx

Переходим к серверу nginx.test.ru. Обновляем пакеты и ставим Nginx:

apt-get update -y && apt-get upgrade -y && apt-get dist-upgrade -y
sudo apt install nginx -y

Подготовим файлы SSL-сертификатов:

mkdir /etc/nginx/ssl

Размещаем в каталоге открытую часть сертификата (proxy.crt) и приватный ключ без пароля (proxy_private.key):

/etc/nginx/ssl/proxy.crt
/etc/nginx/ssl/proxy_private.key

Генерируем файл параметров Диффи-Хеллмана (рекомендуемая минимальная длина ключа — 4096 бит). Обычно этот процесс занимает несколько минут:  Ключи DH будут помещены в /etc/nginx/ssl/dhparam.pem

sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096

Создаем файл настроек виртуального хоста:

nano /etc/nginx/sites-available/test.indeed.local

Ниже приведен стендовый конфиг, на котором мы отлаживали механику взаимодействия компонентов. В продакшене часть параметров должна быть ужесточена: включен PKCE, проверка сертификатов, флаги secure/httponly для cookie.

Полный стендовый конфиг с подробным разбором каждого блока.

server {
   underscores_in_headers off;
   # Запрещаем нижние подчеркивания в HTTP-заголовках. Старые версии Apache и некоторые ретро-серверы по умолчанию отбрасывают заголовки с подчеркиваниями, считая их невалидными. Если забыть эту строку, можно потерять половину пользовательского контекста.

   listen 443 ssl;
   server_name test.indeed.local;

   ssl_certificate /etc/ssl/private/proxy.crt;
   ssl_certificate_key /etc/nginx/ssl/proxy_private.key;

   # === Буферы -- главные грабли проекта ===

   large_client_header_buffers 8 512k;
   proxy_buffering on;
   proxy_buffer_size 512k;
   proxy_buffers 8 512k;
   proxy_busy_buffers_size 512k;
   proxy_max_temp_file_size 2048m;
   proxy_temp_file_write_size 512k;
   # JWT-токены в корпоративной среде обрастают гигантским количеством claim-ов: списки ролей, идентификаторы групп из Active Directory, права доступа к десяткам подсистем, метаданные об устройстве из Indeed AM. Если размер заголовка Cookie превышает лимит стандартного буфера, Nginx молча обрывает соединение или выплевывает 400 Bad Request. Мы увеличили до 8 буферов по 512 КБ (суммарно 4 МБ на заголовки).

   access_log /var/log/nginx/access.log;
   error_log /var/log/nginx/error.log;

   # === Маршрутизация OAuth2 Proxy ===

   location /oauth2/ {
       # Все запросы, начинающиеся с /oauth2/ (кроме явно указанных ниже).
       proxy_pass       https://oauth2.test.ru:4180;
       proxy_set_header Host                    $host;
       proxy_set_header X-Real-IP               $remote_addr;
       proxy_set_header X-Auth-Request-Redirect $scheme://$host:443$request_uri;
       proxy_set_header X-Forwarded-Port        443;
   }

   location = /oauth2/auth {
       # Точное совпадение, сюда auth_request делает внутренние подзапросы.
       proxy_pass                https://oauth2.test.ru:4180;
       proxy_set_header Host     $host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-Uri $request_uri;

       proxy_set_header Content-Length "";
       proxy_pass_request_body   off;
       # Для проверки аутентификации не нужно гонять тело запроса (POST-данные, файлы). Прокси интересуют только заголовки с куками.

       proxy_set_header X-Forwarded-Port 443;
       proxy_set_header X-Auth-Request-Redirect $scheme://$host:443$request_uri;
   }

   location /oauth2/sign_out {
       # Обработка выхода из системы.
       proxy_pass       https://oauth2.test.ru:4180;
       proxy_set_header Host                    $host;
       proxy_set_header X-Real-IP               $remote_addr;
       proxy_set_header X-Forwarded-Uri         $request_uri;
       proxy_set_header X-Forwarded-Port        443;
       proxy_set_header X-Auth-Request-Redirect $scheme://$host:443$request_uri;
   }

   # === Основной блок -- защищаемое приложение ===

   location / {
       resolver 10.10.10.10;
       # DNS-резолвер -- нужен, если в proxy_pass используется доменное имя.

       set $app_server "";

       proxy_pass <имя сервиса>;
       # Замените на реальный адрес бэкенда.

       proxy_set_header X-Real-IP               $remote_addr;
       proxy_set_header X-Auth-Request-Redirect ;

       add_header Cache-Control 'no-store, no-cache';
       # Запрещаем кэширование.

       proxy_connect_timeout 1800s;
       proxy_send_timeout    1800s;
       proxy_read_timeout    1800s;
       # ВНИМАНИЕ: Все три таймаута выкручены на 30 минут, это много. Для прода будет достаточно 60-90 секунд.

       proxy_request_buffering off;

       # --- Предаутентификация ---

       auth_request /oauth2/auth;
       # Включаем проверку: перед доступом к контенту Nginx делает подзапрос.

       error_page 401 =302 /oauth2/sign_in;
       # Если подзапрос вернул 401, то ловим ошибку и отдаем 302 на страницу входа.

       # --- Извлечение данных из ответа auth_request ---

       auth_request_set $user   $upstream_http_x_auth_request_user;
       auth_request_set $email $upstream_http_x_auth_request_email;
       auth_request_set $token $upstream_http_x_auth_request_access_token;
       auth_request_set $auth_cookie $upstream_http_set_cookie;
       # Сохраняем заголовки из ответа OAuth2 Proxy во внутренние переменные Nginx.

       add_header Set-Cookie $auth_cookie;
       # Устанавливаем куки аутентификации клиенту.

       # --- Обработка множественных кук ---

       auth_request_set $auth_cookie_name_upstream_1 $upstream_cookie_auth_cookie_name_1;

       if ($auth_cookie ~* "(; .*)") {
           set $auth_cookie_name_0 $auth_cookie;
           set $auth_cookie_name_1 "auth_cookie_name_1=$auth_cookie_name_upstream_1$1";
       }

       if ($auth_cookie_name_upstream_1) {
           add_header Set-Cookie $auth_cookie_name_0;
           add_header Set-Cookie $auth_cookie_name_1;
       }

       # --- Передача данных аутентификации бэкенду ---

       proxy_set_header Host             $host;
       proxy_set_header X-Forwarded-Host $host;
       proxy_set_header X-User           $user;
       proxy_set_header X-Email          $email;
       proxy_set_header X-Access-Token   $token;
       # Бэкенд получает чистый HTTP-запрос с заголовком X-User: ivan.ivanov.
       # Nginx должен перезаписывать эти заголовки. Без  proxy_set_header атакующий может подсунуть поддельный заголовок X-User: admin, и легаси-бэкенд послушно отдаст админ-панель.

       ### DEBUG -- только для стенда!
       add_header X-Debug-Token $token;
       add_header X-Debug-User  $user;
       add_header X-Debug-Email $email;
       # ВНИМАНИЕ: В продакшене эти заголовки необходимо убрать. Они раскрывают конфиденциальную информацию о токенах и сессиях.
   }
}

Чтобы проверить работу авторизации, переходим по ссылке: https://test.indeed.local. Если все сделано правильно, в результате должно произойти внутреннее перенаправление на страницу входа.

Алгоритм отладки

Сложность отладки предаутентификации заключается в самой архитектуре этого механизма. Мы создали цепочку из независимых узлов, каждый из которых общается с другим через HTTP-запросы, редиректы и криптографические токены, так что приходилось методично по цепочке изучать поведение всех трех компонентов.

Сначала отключали Nginx и стучались напрямую на локальный порт 4180 (OAuth2 Proxy), чтобы проверить чистую схему авторизации и редиректы к IdP. Затем отключили провайдера и проверяли связку Nginx + Proxy.

Чтобы было проще, добавили в конфигурацию Nginx отладочные заголовки (они есть в конфиге выше, в секции DEBUG). Благодаря ним через инспектор браузера видно, какие именно данные (claims) прокси извлекает из JWT и передает балансировщику.

В итоге мы пришли к такому алгоритму:

  1. Открываем консоль разработчика в браузере (вкладка Network), идем на целевой ресурс, и смотрим, куда нас отправил сервер. Получили ли мы 302 Found? Если да, то куда ведет этот редирект, на локальный прокси или сразу на Identity Provider?

  2. Заглядываем в access.log и error.log самого Nginx, и ищем, увидел ли балансировщик наш изначальный запрос. 

  3. Переключаемся на логи OAuth2 Proxy. Что именно пришло к нему от Nginx? Увидел ли прокси куку авторизации в заголовках? Если куки нет, мы пытаемся понять логику демона: почему он решил перенаправить пользователя именно на этот конкретный URL провайдера (Indeed AM)?

  4. Проверяем, произошла ли успешная авторизация в консоли Identity Provider. Выдал ли Indeed AM токен и отправил ли пользователя обратно на Callback URL прокси-сервера?

  5. Возвращаемся в браузер и проверяем, работает ли авторизация. . Пользователь вернулся с токеном, но пустила ли его система? Если нет, начинаем цикл заново, только теперь ищем в логах прокси причину, по которой наш механизм отверг свежей выпущенную сессию.

Итоги и ограничения схемы

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

Вынеся аутентификацию за пределы приложения, мы получили механизм внешней предаутентификации для legacy-системы без масштабного рефакторинга. Он не заменяет встроенную модель авторизации на стороне приложения, но позволяет резко поднять уровень контроля доступа и внедрить MFA там, где сам бэкенд этого не умеет. Однако, за удобство приходится расплачиваться появлением новой точки отказа и усложнением отладки и сопровождения.

Если вам тоже приходилось решать похожую задачу, интересно сравнить подходы: выносили аутентификацию на периметр, закрывали legacy за VPN или все же шли в рефакторинг приложения? Напишите в комментариях, какой в вашем случае оказалась цена такого компромисса