oauth2-proxy — это не SSO, хотя ставят его именно ради него
Кто-то в чате посоветовал «просто повесить oauth2-proxy перед Grafana, и будет SSO» — и через пару недель у вас пять отдельных инстансов прокси, пять cookie-доменов и ни одного места, где посмотреть список пользователей с доступом. oauth2-proxy действительно закрывает вход одной командой и правда работает через Google или GitHub — но SSO-платформой он не станет, сколько сервисов к нему ни подключай, и это стоит понять до того, как архитектура на нём разрастётся.
Содержание
- Что делает oauth2-proxy: reverse-proxy аутентификация, а не identity-платформа
- Установка: nginx + oauth2-proxy + Google OAuth за 20 минут
- Чего в oauth2-proxy нет: пользователей, групп, панели администратора
- Второй сервис — второй инстанс: как масштабируется архитектура
- Общий cookie-домен и его подводные камни
- Когда oauth2-proxy — правильный выбор, а когда нужен Keycloak или Authelia
Что делает oauth2-proxy: reverse-proxy аутентификация, а не identity-платформа
oauth2-proxy (форк Bitly, сейчас живёт под oauth2-proxy/oauth2-proxy) — это обратный прокси-аутентификатор. Он встаёт перед вашим приложением, перехватывает запрос, проверяет, залогинен ли пользователь, и если нет — отправляет его на страницу входа внешнего OAuth/OIDC-провайдера: Google, GitHub, GitLab, Azure AD, Okta или любого другого, который говорит по OIDC. После успешного логина он ставит cookie сессии и пропускает запрос дальше, к вашему приложению.
Это буквально всё, что он делает. Внутри у него нет базы пользователей — есть только список email-адресов или доменов, которым разрешён вход (--email-domain, --authenticated-emails-file). Нет ролей и групп в собственном смысле — он может прокинуть группы, которые вернул провайдер (например, группы Google Workspace или GitHub-организации), но не умеет их создавать и редактировать. Нет админ-панели — вся конфигурация живёт в переменных окружения или флагах командной строки одного процесса.
Технически это middleware для протокола auth_request в nginx (или ForwardAuth в Traefik, или аналога в Caddy): ваш веб-сервер на каждый запрос спрашивает у oauth2-proxy «этот человек залогинен?», получает 200 или 401 и уже сам решает, пускать ли запрос к бэкенду. Сам oauth2-proxy ничего не знает о том, что стоит за прокси — ни о ролях в приложении, ни о том, какие данные видит пользователь внутри.
Модель заимствования доверия здесь простая и честная: oauth2-proxy не хранит пароли и не управляет учётными записями — он делегирует это Google/GitHub/Okta целиком. Если у вас уже есть Google Workspace для команды, это ровно то, что нужно: минимум инфраструктуры, максимум доверия внешнему провайдеру, который и так отвечает за учётки сотрудников.
Установка: nginx + oauth2-proxy + Google OAuth за 20 минут
Показательно, насколько это просто — и насколько соблазн «это же SSO» силён именно из-за простоты.
Создайте OAuth-клиент в Google Cloud Console (APIs & Services → Credentials → OAuth client ID, тип Web application), укажите Authorized redirect URI вида https://app.example.com/oauth2/callback. Получите client_id и client_secret.
Минимальный docker-compose.yml:
services:
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:latest
restart: unless-stopped
environment:
OAUTH2_PROXY_PROVIDER: google
OAUTH2_PROXY_CLIENT_ID: ${GOOGLE_CLIENT_ID}
OAUTH2_PROXY_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
OAUTH2_PROXY_EMAIL_DOMAINS: example.com
OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
OAUTH2_PROXY_COOKIE_SECURE: "true"
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
OAUTH2_PROXY_UPSTREAMS: http://app:8080
OAUTH2_PROXY_REDIRECT_URL: https://app.example.com/oauth2/callback
OAUTH2_PROXY_SET_XAUTHREQUEST: "true"
ports:
- "4180:4180"
COOKIE_SECRET — 32-байтовая случайная строка, генерируется так:
python3 -c 'import secrets,base64; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())'
Конфиг nginx перед приложением:
location /oauth2/ {
proxy_pass http://127.0.0.1:4180;
proxy_set_header Host $host;
proxy_set_header X-Auth-Request-Redirect $request_uri;
}
location = /oauth2/auth {
internal;
proxy_pass http://127.0.0.1:4180;
proxy_set_header Host $host;
proxy_pass_request_body off;
}
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
auth_request_set $email $upstream_http_x_auth_request_email;
proxy_set_header X-Email $email;
proxy_pass http://app:8080;
}
Это всё — после docker compose up -d вход по Google-логину уже работает, приложение получает email пользователя в заголовке X-Email. Ровно на этом шаге многие и делают вывод «SSO готово» — потому что вход действительно единый, если провайдер один и приложение одно.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверЧего в oauth2-proxy нет: пользователей, групп, панели администратора
Дальше начинается разница между «единый вход в один сервис» и SSO-платформой.
Полноценный identity-провайдер (Keycloak, Authelia, Authentik, Okta) хранит свою базу пользователей — или федерирует её с LDAP/AD — и даёт админке возможность: создать пользователя вручную, посадить его в группу, отозвать доступ одной кнопкой, посмотреть журнал входов, настроить MFA-политику по группам, выдать временный доступ подрядчику. oauth2-proxy ничего этого не делает — у него просто нет хранилища состояния пользователей, только stateless-проверка «есть валидный токен от провайдера — пускаем».
Практические следствия:
- Отозвать доступ конкретному человеку можно только через сам OAuth-провайдер (убрать из Google Workspace / GitHub-организации) или вручную вычеркнуть его email из
--authenticated-emails-fileи перезапустить/перечитать конфиг прокси. Единой кнопки «отключить доступ везде» нет — она есть в Google Admin, но не в oauth2-proxy. - Ролей внутри приложения oauth2-proxy не выдаёт — он в лучшем случае прокидывает группы, которые уже существуют у провайдера. Если вам нужна ролевая модель именно для доступа к разным сервисам (этому — только чтение логов, этому — админка целиком), придётся городить это на стороне каждого приложения самостоятельно.
- Аудита входов как отдельного интерфейса нет — есть только логи процесса oauth2-proxy в stdout, которые нужно собирать и парсить самому (например, через
--request-logging=trueи последующую отправку в Loki/ELK). - MFA — это ответственность провайдера (Google, GitHub), а не oauth2-proxy. Если провайдер не требует второй фактор, oauth2-proxy тоже не потребует.
Ничего из этого не «баг» — это осознанный дизайн: oauth2-proxy — тонкий gatekeeper, а не платформа управления идентификацией. Но именно это отличие обычно всплывает не на этапе выбора, а через полгода, когда команда выросла и кто-то спрашивает «а как нам посмотреть, у кого есть доступ к продовому Grafana».
Второй сервис — второй инстанс: как масштабируется архитектура
Вот где разница между oauth2-proxy и настоящим SSO становится физически ощутимой. У Keycloak или Authelia один центральный сервер аутентификации, к которому подключаются N приложений через OIDC/SAML — конфигурация пользователей, групп и политик в одном месте.
У oauth2-proxy архитектура принципиально другая: на каждый защищаемый сервис — свой экземпляр процесса (или как минимум своя конфигурация с отдельным upstream). Хотите закрыть логином Grafana, Prometheus и внутреннюю wiki — получаете три инстанса oauth2-proxy (или один инстанс, слушающий несколько портов с разными upstream, что тоже требует ручной настройки под каждый сервис отдельно).
Пример: для Grafana и wiki это два отдельных блока сервиса в docker-compose — каждый со своим OAUTH2_PROXY_UPSTREAMS, своим OAUTH2_PROXY_COOKIE_NAME (_oauth2_grafana / _oauth2_wiki), своим OAUTH2_PROXY_REDIRECT_URL и своим портом (4180:4180, 4181:4180), но с повторением всех остальных параметров провайдера.
Каждый инстанс — это отдельный client_id/client_secret в OAuth-провайдере (или один client_id с несколькими redirect URI, если провайдер это позволяет), отдельная cookie, отдельный список разрешённых email. Изменение политики доступа («добавить нового сотрудника ко всем сервисам») превращается в правку N конфигов вместо одной записи в одной админке.
Это не значит, что схема плохая — для 1-3 сервисов она абсолютно рабочая и даже проще, чем разворачивать Keycloak. Но называть её SSO в строгом смысле («single sign-on через единую систему управления идентификацией») некорректно: точнее — «единый вход в *каждый* сервис по одному и тому же внешнему аккаунту», что для пользователя выглядит похоже, а для администратора — совсем не похоже.
Общий cookie-домен и его подводные камни
Есть способ частично сгладить проблему множественных логинов — общий cookie-домен. Если все сервисы живут на поддоменах одного домена (grafana.example.com, wiki.example.com), можно задать:
OAUTH2_PROXY_COOKIE_DOMAINS: .example.com
OAUTH2_PROXY_WHITELIST_DOMAINS: .example.com
Тогда cookie сессии, выданная на одном поддомене, будет читаться и на остальных — пользователь, залогинившись один раз на grafana.example.com, при переходе на wiki.example.com не увидит формы логина заново (если, конечно, у обоих инстансов совпадает cookie_secret и cookie_name, иначе оба всё равно не «увидят» одну и ту же сессию).
Это уже ближе к ощущению единого входа для пользователя. Но обратите внимание на нюансы:
cookie_secretдолжен быть один и тот же на всех инстансах, которые должны делить сессию — иначе шифрование cookie не совпадёт и каждый инстанс будет считать, что пользователь не залогинен.cookie_nameтоже должен совпадать между инстансами, иначе браузер отправит cookie, которую целевой инстанс просто не будет искать под своим именем.- Каждый сервис всё равно физически ходит в OAuth-провайдер для проверки токена (или доверяет локальной валидации JWT/cookie) — это не «единая сессия на сервере», а N независимых процессов, синхронизированных только общим секретом шифрования cookie. Если один инстанс перезапущен с новым
cookie_secretпо ошибке — пользователи именно этого сервиса вылетят из сессии, остальные не заметят. - Управление доступом всё ещё остаётся раздельным per-инстанс: общая cookie ускоряет вход, но не создаёт общий список пользователей и групп.
Иными словами, общий cookie-домен решает проблему UX (не логиниться на каждый сервис заново), но не решает проблему администрирования (нет единой точки, где выдать/забрать доступ). Это тонкая, но принципиальная разница с настоящим SSO.
Когда oauth2-proxy — правильный выбор, а когда нужен Keycloak или Authelia
Практическое правило: считайте сервисы и считайте, сколько раз в месяц вы что-то меняете в доступах.
| Критерий | oauth2-proxy | Keycloak / Authelia |
|---|---|---|
| Число защищаемых сервисов | 1-3 | 4+ |
| Своя база пользователей | Нет (доверяем Google/GitHub) | Да, или федерация с LDAP/AD |
| Роли внутри приложений | Нет, только группы провайдера | Да, полноценные роли и группы |
| Единая админка для доступов | Нет | Да |
| Время развёртывания | 20-30 минут | Часы, нужны БД и продуманные realm/policy |
| Требования к ресурсам | Минимальны, десятки МБ RAM | Заметно выше — Java или Go плюс отдельная СУБД |
oauth2-proxy — правильный выбор, когда:
- у вас 1-3 внутренних сервиса и все пользователи уже есть в одном Google Workspace/GitHub-организации;
- вам не нужна ролевая модель внутри приложения — достаточно факта «этот email из нашей компании»;
- вы не готовы держать отдельную БД и процесс ради identity-провайдера ради пары дашбордов.
Нужен полноценный IdP (Keycloak/Authelia/Authentik), когда:
- сервисов становится 4+ и правка N конфигов на каждое изменение доступа перестаёт масштабироваться;
- нужна федерация с корпоративным LDAP/AD или SAML для B2B-клиентов — этого у oauth2-proxy нет вообще;
- нужен централизованный аудит входов, единая MFA-политика, self-service сброс пароля;
- команда достаточно большая, чтобы ротация сотрудников (найм/увольнение) требовала единой точки отзыва доступа.
Разница между Keycloak и Authentik или между Authelia и Authentik — это выбор между полноценными IdP-платформами. oauth2-proxy в этом сравнении не участвует в принципе: это инструмент другого класса, ближе к «замку на одну дверь», чем к «системе пропусков на здание». Если вы уже понимаете, что вам нужен именно IdP, разумно сразу смотреть в сторону установки Keycloak или Authelia, а не пытаться дорастить oauth2-proxy до того, чем он не является.
Встречается и гибридная схема: oauth2-proxy как ForwardAuth-мост перед сервисами, которые сами по себе не понимают OIDC, а за ним — Keycloak как провайдер. Это законная конфигурация (oauth2-proxy поддерживает --provider=oidc с указанием на любой OIDC-совместимый issuer, включая свой Keycloak), но тогда управление пользователями всё равно ведёт Keycloak, а oauth2-proxy остаётся тем же тонким gatekeeper — просто источник доверия меняется с Google на ваш собственный IdP.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
oauth2-proxy бесплатный?
Да, open source под Apache 2.0, без ограничений на число пользователей или сервисов.
Можно использовать несколько провайдеров одновременно (и Google, и GitHub)?
Один экземпляр настраивается на одного провайдера через --provider. Для нескольких вариантов входа нужны либо несколько инстансов на разных доменах, либо провайдер-агрегатор (Keycloak, Auth0), к которому oauth2-proxy подключается одним OIDC-клиентом.
oauth2-proxy хранит пароли пользователей?
Нет — вся аутентификация на стороне внешнего провайдера, oauth2-proxy только проверяет токен и ставит свою сессионную cookie.
Что будет, если провайдер станет недоступен?
Новые входы невозможны до восстановления провайдера. Уже залогиненные пользователи с валидной cookie продолжат работать до истечения сессии (--cookie-expire), если не настроена постоянная ревалидация токена.
Можно подключить oauth2-proxy к Traefik вместо nginx?
Да, через middleware ForwardAuth на http://oauth2-proxy:4180/oauth2/auth с пробросом X-Forwarded-Uri — логика та же, что и с auth_request в nginx.
Нужна отдельная БД для oauth2-proxy?
В базовом режиме нет — сессии хранятся в зашифрованной cookie у клиента. Для больших payload'ов можно подключить Redis как session store (--session-store-type=redis), но это не превращает oauth2-proxy в хранилище пользователей, только в хранилище активных сессий.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →