Когда хочется единой точки входа в админку и внутренние панели, но затевать полноценный 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.
Высокоуровневый поток:
- Клиент приходит в Nginx на защищённый путь. Nginx делает
auth_requestк/oauth2/auth. oauth2-proxyпроверяет сессионную cookie. Если валидна — 200 и заголовки с атрибутами; если нет — 401.- При 401 Nginx редиректит на
/oauth2/start?rd=..., oauth2-proxy ведёт пользователя к провайдеру OIDC, получает ID-токен, проставляет cookie и возвращает на нужный URL. - Дальше все запросы на защищённые пути проходят прозрачно, а 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 проще работать с claimgroupsиз токена.
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и т. д.).

Контроль доступа: домены, группы и списки
В oauth2-proxy есть несколько уровней контроля:
email_domains— пустит пользователей с адресами из указанных доменов.allowed_groups— потребует наличие одной из групп в claimgroups. Для 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.
Логи и заголовки для бэкенда
Пробрасывайте в приложение минимум два заголовка: 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 так, чтобы пользователи не «выпадали» в середине длительного соединения.

Высокая доступность и масштабирование
oauth2-proxy статичен и лёгок. Запускайте минимум две реплики и балансируйте через Nginx upstream. Сессии хранятся в зашифрованной cookie — горизонтальное масштабирование тривиально. Не забывайте про keepalive к upstream и мониторинг готовности (статусные эндпоинты на 4181).
Частые проблемы и их признаки
- Петля /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у провайдера и обновляйте его в конфиге.
Пошаговый чек-лист внедрения
- Создайте приложение у провайдера (Google/Microsoft), получите
client_id/client_secret, пропишите корректныйredirect_url. - Соберите
/etc/oauth2-proxy/config.yaml, задайтеcookie_secret, домены, группы. - Поднимите oauth2-proxy как сервис, проверьте, что он слушает 4180/4181 и отдаёт 200 на
/pingи 401 на/oauth2/authбез cookie. - Настройте Nginx:
/oauth2/authкакinternal, префикс/oauth2/на прокси, таймауты, заголовки,error_page 401на@oauth2_signin. - Залогиньтесь с реальным пользователем, проверьте в DevTools cookie, в логах Nginx/oauth2-proxy статусы 200/302/401/403.
- Подключите алерты: рост 401/403, ошибки 5xx у oauth2-proxy, время ответа
/oauth2/auth.
Итог
Связка oauth2-proxy + Nginx auth_request — быстрый способ получить корпоративный SSO на базе OIDC с провайдерами Google/Microsoft. Она даёт предсказуемую производительность, наблюдаемость и прозрачную авторизацию через заголовки. Держите короткие таймауты на /oauth2/auth, аккуратно обращайтесь с cookie и редиректами — и получите устойчивый, безопасный и удобный шлюз доступа для админских разделов и внутренних сервисов.


