Выберите продукт

OAuth2-Proxy + Nginx: SSO через Google/Microsoft и auth_request

Разбираем, как превратить Nginx в шлюз SSO с помощью oauth2-proxy и OIDC: защита админок, маршрутов и бэкендов через Google/Microsoft. Даем реальные конфиги, оптимальные таймауты, анти‑петли редиректов, работу с доменами/группами и cookie.
OAuth2-Proxy + Nginx: SSO через Google/Microsoft и auth_request

Когда хочется единой точки входа в админку и внутренние панели, но затевать полноценный IdP (Keycloak и т. п.) — избыточно, на помощь приходит связка oauth2-proxy + Nginx. Мы получаем SSO на базе OIDC с провайдерами Google или Microsoft Entra ID (Azure AD), а авторизацию в приложениях делаем прозрачно: Nginx спрашивает «кто ты?» у локального oauth2-proxy через auth_request, а дальше пробрасывает атрибуты пользователя в заголовках.

Подход масштабируется, легко дебажится и хорошо ложится на привычную схему обратного прокси. В статье собрал практический рецепт для продакшена: быстрый старт, защита выбранных локаций, надёжные таймауты, группы/домены, антициклы редиректов, обработка 401/403, нюансы cookie и проблемы, которые чаще всего бьют по бою.

Архитектура и поток аутентификации

Компоненты простые:

  • Nginx — основной reverse proxy перед приложениями. Для защищаемых локаций выполняет подзапрос auth_request к локальному /oauth2/auth.
  • oauth2-proxy — маленький сервис, который делает OIDC-танец с провайдером (Google/Microsoft), хранит зашифрованные сессионные cookie и по запросу /oauth2/auth отвечает 200/401/403, а также отдаёт заголовки с данными пользователя.
  • Провайдер OIDC — Google или Microsoft Entra ID. Вы создаёте приложение, получаете client_id/client_secret, настраиваете redirect URI.

Высокоуровневый поток:

  1. Клиент приходит в Nginx на защищённый путь. Nginx делает auth_request к /oauth2/auth.
  2. oauth2-proxy проверяет сессионную cookie. Если валидна — 200 и заголовки с атрибутами; если нет — 401.
  3. При 401 Nginx редиректит на /oauth2/start?rd=..., oauth2-proxy ведёт пользователя к провайдеру OIDC, получает ID-токен, проставляет cookie и возвращает на нужный URL.
  4. Дальше все запросы на защищённые пути проходят прозрачно, а Nginx может прокинуть в бэкенд заголовки вроде X-Auth-Request-User, X-Auth-Request-Email, X-Auth-Request-Groups.

Важно: auth_request — это лёгкий подзапрос к локальному oauth2-proxy, а не к внешнему IdP. При валидной cookie провайдер не дёргается, а ответ получается быстро.

Подготовка учётных данных в Google/Microsoft

На стороне провайдера OIDC потребуется зарегистрировать приложение, задать redirect URI и получить Client ID/Client Secret:

  • Google: тип «Веб-приложение», в разрешениях как минимум openid email profile, redirect URI вида https://your-domain.example.com/oauth2/callback.
  • Microsoft Entra ID: регистрируем приложение, включаем ID tokens, настраиваем redirect URI. Для групп можно включить выдачу groups в ID-токен или использовать Graph для групп, но в oauth2-proxy проще работать с claim groups из токена.

URI обратного вызова (/oauth2/callback) должен указывать на хост, где фронтируется oauth2-proxy (обычно тот же виртуальный хост, что и приложение, через префикс /oauth2/...).

Установка и запуск oauth2-proxy

Ниже конфиг в формате YAML с двумя провайдерами (Google, Microsoft). Один из них можно оставить активным, второй — удалить. Режим — auth-only (static://200), чтобы сервис был «чистым» авторизационным эндпоинтом для Nginx.

# /etc/oauth2-proxy/config.yaml
server:
  bind_address: "127.0.0.1:4180"
  metrics_address: "127.0.0.1:4181"
  secure_cookies: true
  cookie:
    name: "_oauth2_proxy"
    secret: "base64-32-bytes-secret-here"
    expire: 12h
    refresh: 1h
    same_site: "Lax"

upstreams:
  - "static://200"

# Проксируем атрибуты для Nginx auth_request
inject_request_headers:
  - name: "X-Auth-Request-User"
    values:
      - claim: "preferred_username"
      - claim: "email"
  - name: "X-Auth-Request-Email"
    values:
      - claim: "email"
  - name: "X-Auth-Request-Groups"
    values:
      - claim: "groups"

providers:
  - id: "google"
    provider: "oidc"
    client_id: "your-google-client-id"
    client_secret: "your-google-client-secret"
    oidc_issuer_url: "https://accounts.google.com"
    scope:
      - "openid"
      - "email"
      - "profile"

  - id: "azure"
    provider: "oidc"
    client_id: "your-azure-client-id"
    client_secret: "your-azure-client-secret"
    oidc_issuer_url: "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
    scope:
      - "openid"
      - "email"
      - "profile"

# Базовые политики
email_domains:
  - "example.com"
allowed_groups:
  - "admins"
  - "devops"

# Настройки редиректов и безопасности
redirect_url: "https://app.example.com/oauth2/callback"
whitelist_domains:
  - ".example.com"
reverse_proxy: true
pass_authorization_header: false
set_authorization_header: false
skip_jwt_bearer_tokens: true

# Лимиты и диагностика
standard_logging: true
request_logging: true
silence_ping_logging: true

Сервис под systemd:

# /etc/systemd/system/oauth2-proxy.service
[Unit]
Description=oauth2-proxy
After=network-online.target
Wants=network-online.target

[Service]
User=oauth2proxy
Group=oauth2proxy
ExecStart=/usr/local/bin/oauth2-proxy --config /etc/oauth2-proxy/config.yaml
Restart=always
RestartSec=2s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
CapabilityBoundingSet=
AmbientCapabilities=
LockPersonality=true

[Install]
WantedBy=multi-user.target

Проверьте, что cookie_secret действительно 32 байта, закодированные в base64. Пользователь сервиса должен иметь доступ к конфигу и секретам, но не обладать лишними правами. Для изоляции стенда удобно поднять отдельный инстанс на VDS. Для боевого трафика включайте HTTPS с корректными цепочками и современными алгоритмами — пригодятся SSL-сертификаты.

Nginx: включаем auth_request и аккуратные таймауты

Далее — минимально самодостаточный фрагмент. Он защищает все пути, редиректит неаутентифицированных на /oauth2/start, прокидывает атрибуты пользователя и аккуратно выставляет таймауты для подзапроса.

# upstream для oauth2-proxy
upstream oauth2_proxy {
    server 127.0.0.1:4180;
    keepalive 16;
}

# Рекомендуется увеличить лимиты заголовков, если атрибутов/куки много
large_client_header_buffers 8 16k;

server {
    listen 443 ssl http2;
    server_name app.example.com;

    # ... ssl_certificates, security headers ...

    # Таймауты для "боевого" запроса к бэкенду подбирайте отдельно

    # Внутренняя локация для проверки авторизации
    location = /oauth2/auth {
        internal;
        proxy_pass http://oauth2_proxy/oauth2/auth;

        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header X-Original-Method $request_method;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;

        # Акуратные таймауты: auth должен отвечать быстро
        proxy_connect_timeout 0.3s;
        proxy_read_timeout 0.8s;
        proxy_send_timeout 0.8s;

        proxy_http_version 1.1;
    }

    # Вход в SSO и callback обслуживаются самим oauth2-proxy под префиксом /oauth2/
    location /oauth2/ {
        proxy_pass http://oauth2_proxy;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_http_version 1.1;

        proxy_connect_timeout 1s;
        proxy_read_timeout 5s;
        proxy_send_timeout 5s;
    }

    # Сохраняем из auth_request нужные атрибуты
    auth_request_set $auth_user   $upstream_http_x_auth_request_user;
    auth_request_set $auth_email  $upstream_http_x_auth_request_email;
    auth_request_set $auth_groups $upstream_http_x_auth_request_groups;

    # Защищаем весь сайт (можно сузить до /admin и т. п.)
    location / {
        auth_request /oauth2/auth;

        error_page 401 = @oauth2_signin;
        error_page 403 = @oauth2_forbidden;

        # Пробрасываем в бэкенд атрибуты пользователя
        proxy_set_header X-Auth-Request-User   $auth_user;
        proxy_set_header X-Auth-Request-Email  $auth_email;
        proxy_set_header X-Auth-Request-Groups $auth_groups;

        proxy_pass http://app_backend;

        # Таймауты для основного бэкенда — отдельно от auth
        proxy_connect_timeout 2s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
    }

    location @oauth2_signin {
        return 302 /oauth2/start?rd=$scheme://$host$request_uri;
    }

    location @oauth2_forbidden {
        add_header Content-Type text/plain;
        return 403 "Forbidden\n";
    }
}

Замечания:

  • auth_request держим «коротким»: если локальный oauth2-proxy не ответил за доли секунды, лучше отдать ошибку, чем подвесить воркеры Nginx.
  • В локации /oauth2/ ставим более мягкие таймауты: тут идут редиректы и обмен кодов/токенов с провайдером.
  • Если защищать не весь сайт, перенесите auth_request в нужные location (/admin, /internal и т. д.).

Диаграмма потока auth_request: Nginx, oauth2-proxy и OIDC провайдер

Контроль доступа: домены, группы и списки

В oauth2-proxy есть несколько уровней контроля:

  • email_domains — пустит пользователей с адресами из указанных доменов.
  • allowed_groups — потребует наличие одной из групп в claim groups. Для Google группы в ID-токен обычно не приходят; для Entra ID — можно включить; иначе используйте статический список email’ов (authenticated_emails_file).
  • whitelist_domains — ограничение доменов для редиректов (rd=), чтобы пресечь открытые редиректы.

Хорошая стратегия: на уровне oauth2-proxy сузить круг «кто в принципе может войти», а бизнес-правила дореализовать в приложении на основе заголовков X-Auth-Request-*.

Защита от петель и тонкие места редиректов

Циклы встречаются чаще всего из-за неправильного redirect_url, несовпадения хостов/протоколов в заголовках X-Forwarded-* или потому, что cookie не проставляется.

  • Убедитесь, что redirect_url и фактический путь /oauth2/callback совпадают по схеме и хосту.
  • Всегда прокидывайте X-Forwarded-Proto и X-Forwarded-Host к oauth2-proxy, а также включите reverse_proxy: true.
  • Проверьте флаг secure_cookies: true: без HTTPS браузер может отбрасывать cookie; для мультидомена используйте whitelist_domains и корректные cookie-параметры.
  • SameSite: для обычных сценариев хватит Lax. Если редиректы происходят между разными поддоменами — возможно потребуется None и Secure.

Про таймауты: быстрый auth и стабильный бэкенд

Ключевая цель — не допустить блокировки воркеров Nginx при зависшем auth-сервисе и одновременно не резать живые сессии:

  • auth_request: proxy_connect_timeout 200–300ms, proxy_read_timeout 500–800ms. Это подзапрос к локальному процессу — он должен отвечать быстро. Если нет, отдаём 503 и пишем алерт.
  • /oauth2/ (редиректы и обмен кодов): увеличьте таймауты до 3–10 секунд — возможны сетевые походы к провайдеру.
  • бэкенд: свои таймауты по профилю приложения. Для gRPC/стриминга — отдельные значения.

Не увлекайтесь микрокэшем на /oauth2/auth: кэширование 200/401/403 на секунды может дать побочные эффекты (особенно при смене групп/прав). Если и использовать — то осознанно и с дискриминацией по cookie.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Логи и заголовки для бэкенда

Пробрасывайте в приложение минимум два заголовка: X-Auth-Request-User и X-Auth-Request-Email. Если используете группы — X-Auth-Request-Groups. На стороне бэкенда логируйте их вместе с X-Request-ID для трассировки инцидентов. В Nginx можно вывести эти значения и в access_log формат — удобно для быстрой проверки, кто ходит в /admin.

Вебсокеты и долгие соединения

Аутентификация происходит на этапе установления соединения (GET-запрос перед апгрейдом). После апгрейда auth_request уже не участвует. Настройте cookie.refresh и cookie.expire так, чтобы пользователи не «выпадали» в середине длительного соединения.

Фрагменты systemd-сервиса и конфигурации Nginx для oauth2-proxy

Высокая доступность и масштабирование

oauth2-proxy статичен и лёгок. Запускайте минимум две реплики и балансируйте через Nginx upstream. Сессии хранятся в зашифрованной cookie — горизонтальное масштабирование тривиально. Не забывайте про keepalive к upstream и мониторинг готовности (статусные эндпоинты на 4181).

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Частые проблемы и их признаки

  • Петля /oauth2/start → провайдер → /oauth2/callback → снова /oauth2/start: проверьте проставление cookie (DevTools), заголовки X-Forwarded-Proto, reverse_proxy, домен cookie и redirect_url.
  • 403 после логина: пользователь прошёл аутентификацию, но не удовлетворяет политикам (email_domains, allowed_groups).
  • 401 на защищённых путях спустя 1 час: истёк cookie.refresh или cookie.expire; либо браузер чистит cookie из-за политики SameSite.
  • 400/431 Request Header Fields Too Large: увеличьте large_client_header_buffers и проверьте размер cookie/заголовков.
  • Подвисания Nginx: слишком большие таймауты на /oauth2/auth и нехватка воркеров. Уменьшите таймауты, включите алерты по росту активных соединений.

Разделение доступа по путям

Иногда нужно, чтобы /admin был доступен только группе admins, а /reports — ещё и devops. Есть два пути:

  • Проверять группы в приложении (проще всего).
  • Поднять второй экземпляр oauth2-proxy с иным набором allowed_groups и повесить его на другой internal-эндпоинт для auth_request конкретного location.

Безопасность: мелочи, которые дают много

  • Генерируйте сильный cookie_secret (32 байта) и храните его в отдельном файле с правами 600.
  • Ограничивайте редиректы whitelist_domains и проверяйте параметр rd на стороне Nginx, если пишете свои ручки.
  • Проставляйте Secure, HttpOnly, корректный SameSite для cookie.
  • Логи oauth2-proxy не должны утекать во внешний мир: не возвращайте их пользователю, храните отдельно, включайте silence_ping_logging.
  • Регулярно пересоздавайте client_secret у провайдера и обновляйте его в конфиге.

Пошаговый чек-лист внедрения

  1. Создайте приложение у провайдера (Google/Microsoft), получите client_id/client_secret, пропишите корректный redirect_url.
  2. Соберите /etc/oauth2-proxy/config.yaml, задайте cookie_secret, домены, группы.
  3. Поднимите oauth2-proxy как сервис, проверьте, что он слушает 4180/4181 и отдаёт 200 на /ping и 401 на /oauth2/auth без cookie.
  4. Настройте Nginx: /oauth2/auth как internal, префикс /oauth2/ на прокси, таймауты, заголовки, error_page 401 на @oauth2_signin.
  5. Залогиньтесь с реальным пользователем, проверьте в DevTools cookie, в логах Nginx/oauth2-proxy статусы 200/302/401/403.
  6. Подключите алерты: рост 401/403, ошибки 5xx у oauth2-proxy, время ответа /oauth2/auth.

Итог

Связка oauth2-proxy + Nginx auth_request — быстрый способ получить корпоративный SSO на базе OIDC с провайдерами Google/Microsoft. Она даёт предсказуемую производительность, наблюдаемость и прозрачную авторизацию через заголовки. Держите короткие таймауты на /oauth2/auth, аккуратно обращайтесь с cookie и редиректами — и получите устойчивый, безопасный и удобный шлюз доступа для админских разделов и внутренних сервисов.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: конфликт systemd-resolved DNSStubListener на 127.0.0.53 с dnsmasq, Unbound и BIND OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: конфликт systemd-resolved DNSStubListener на 127.0.0.53 с dnsmasq, Unbound и BIND

Если локальный DNS в Debian или Ubuntu не стартует с ошибкой address already in use, причина часто в systemd-resolved и DNSStubLis ...
Debian/Ubuntu: как исправить NFS mount.nfs: access denied by server while mounting OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить NFS mount.nfs: access denied by server while mounting

Ошибка mount.nfs: access denied by server while mounting в Debian и Ubuntu обычно указывает на проблему на стороне NFS-сервера: не ...
Debian/Ubuntu: как устранить конфликт systemd-resolved DNSStubListener с BIND9, dnsmasq и AdGuard Home OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как устранить конфликт systemd-resolved DNSStubListener с BIND9, dnsmasq и AdGuard Home

Если в Debian или Ubuntu DNS-сервер не стартует из-за ошибки port 53 busy, часто причина в systemd-resolved с локальным слушателем ...