SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-21

Настройка SSO для любого приложения через oauth2-proxy

Узнайте, как внедрить SSO в приложения без встроенной авторизации с помощью oauth2-proxy. Настройте Forward Auth в Nginx или Traefik, чтобы избежать ошибок с cookie и OIDC.

Forward auth: как добавить SSO в приложение без собственной системы авторизации

oauth2-proxy позволяет реализовать единый вход (SSO) для приложения, в котором нет встроенной системы авторизации. Это работает за счет того, что обратный прокси-сервер перед приложением перехватывает каждый запрос, запрашивает у oauth2-proxy информацию о наличии валидной сессии и передает запрос вышестоящему приложению только в случае положительного ответа. Код приложения при этом не меняется, так как оно не участвует в проверке.

Проверка представляет собой дополнительный HTTP-запрос. Прокси-сервер отправляет копию заголовков входящего запроса в /oauth2/auth и считывает код ответа. Код 202 означает, что у пользователя есть сессия, поэтому прокси пересылает исходный запрос в приложение. Код 401 означает отсутствие сессии, и в этом случае прокси перенаправляет браузер в /oauth2/sign_in, где инициируется процесс входа через OpenID Connect (OIDC) у вашего провайдера идентификации. OIDC — это уровень идентификации, построенный поверх OAuth 2.0, а в качестве провайдера выступает любая система, которую вы уже используете для авторизации.

У этого шаблона есть свое название в каждом обратном прокси. В Nginx эта директива называется auth_request. В Traefik это middleware forwardAuth. В Caddy используется forward_auth. Сервис, отвечающий на подзапросы, также может быть заменен. oauth2-proxy является наиболее распространенным выбором, так как он поддерживает стандартный OIDC и не требует собственной базы данных.

Определите границы доверия перед написанием конфигурации

После успешной проверки oauth2-proxy возвращает данные пользователя в заголовках ответа, а обратный прокси копирует их в запрос к upstream-серверу. При включенном set_xauthrequest вы получаете X-Auth-Request-User и X-Auth-Request-Email. Приложение считывает эти заголовки и доверяет им.

Это вся модель безопасности, поэтому важно четко понимать последствия. Любой субъект, способный установить TCP-соединение с портом приложения, может самостоятельно задать эти заголовки и выдать себя за любого пользователя. Одиночный curl -H "X-Auth-Request-Email: admin@example.com" http://app-host:3000/ означает полный обход защиты, если запрос достигает приложения напрямую.

Следовательно, приложение не должно быть доступно ниоткуда, кроме как через прокси. В Docker Compose удалите маппинг ports: из сервиса приложения и оставьте его только во внутренней сети, чтобы только контейнер прокси мог подключаться к нему. На обычном сервере привяжите приложение к 127.0.0.1:3000 вместо 0.0.0.0:3000. Затем проверьте, что именно вы выставили наружу:

sudo ss -tlnp | grep 3000

Строка с 0.0.0.0:3000 означает, что приложение отвечает на публичном IP-адресе и ваш шлюз является лишь декорацией. 127.0.0.1:3000 — это то, что вам нужно. Правило межсетевого экрана является полезным вторым уровнем защиты, но адрес привязки — это то, что останется в силе, даже если другой инструмент сбросит ваш набор правил.

Установка oauth2-proxy

По состоянию на август 2026 года актуальным релизом является v7.15.3, выпущенный в июне 2026 года. Установите бинарный файл и проверьте загрузку:

cd /tmp
curl -fsSLO https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.15.3/oauth2-proxy-v7.15.3.linux-amd64.tar.gz
curl -fsSLO https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.15.3/oauth2-proxy-v7.15.3.linux-amd64.tar.gz-sha256sum.txt
sha256sum -c oauth2-proxy-v7.15.3.linux-amd64.tar.gz-sha256sum.txt
tar -xzf oauth2-proxy-v7.15.3.linux-amd64.tar.gz
sudo install -m 755 oauth2-proxy-v7.15.3.linux-amd64/oauth2-proxy /usr/local/bin/oauth2-proxy
oauth2-proxy --version

sha256sum -c должен вывести строку, заканчивающуюся на OK. Если выводится FAILED, остановитесь и загрузите файл заново, вместо того чтобы запускать бинарный файл.

В Docker используется образ quay.io/oauth2-proxy/oauth2-proxy, и вам следует зафиксировать тег: quay.io/oauth2-proxy/oauth2-proxy:v7.15.3. Использование latest превращает плановую операцию docker compose pull в незапланированное обновление процесса, который защищает все приложения на сервере.

Сессионная cookie шифруется, а cookie_secret выступает в роли ключа. Его длина должна составлять ровно 16, 24 или 32 байта, так как он используется в качестве ключа AES (Advanced Encryption Standard). При любой другой длине oauth2-proxy откажется запускаться и выдаст ошибку при старте с указанием на cookie secret.

openssl rand -base64 32 | tr -- '+/' '-_'

Использование tr не является формальностью. Эта команда преобразует стандартный base64 в алфавит, безопасный для URL, что позволяет значению корректно передаваться через оболочку, файл переменных окружения и HTTP-заголовки без проблем с экранированием.

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

Настройка oauth2-proxy

Храните параметры в файле, а не в длинной командной строке, чтобы client secret не отображался в выводе ps.

# /etc/oauth2-proxy/oauth2-proxy.cfg
http_address = "127.0.0.1:4180"
reverse_proxy = true

provider = "oidc"
oidc_issuer_url = "https://id.example.com/application/o/myapp/"
client_id = "REPLACE_ME"
client_secret = "REPLACE_ME"

redirect_url = "https://app.example.com/oauth2/callback"
cookie_secret = "REPLACE_ME"
cookie_secure = true
cookie_domains = [".example.com"]
whitelist_domains = [".example.com"]

email_domains = ["*"]
set_xauthrequest = true
upstreams = ["static://202"]

Параметр reverse_proxy = true указывает oauth2-proxy доверять заголовкам X-Forwarded-* от прокси, стоящего перед ним. Без этого oauth2-proxy воспринимает адрес самого прокси как адрес клиента и может неверно определить, был ли запрос выполнен по HTTPS.

Параметр upstreams = ["static://202"] заставляет oauth2-proxy отвечать кодом 202 на аутентифицированный запрос и не проксировать трафик, что идеально подходит для forward auth, так как проксированием занимается reverse proxy. Другой вариант развертывания предполагает размещение oauth2-proxy непосредственно в пути запроса с использованием upstreams = ["http://127.0.0.1:3000"] и без auth_request. Это проще для одного приложения, но не масштабируется до десяти.

Параметр email_domains = ["*"] разрешает доступ для всех адресов, которые аутентифицирует ваш провайдер. Ограничьте его своим доменом или, что еще лучше, настройте ограничение доступа через привязку к группе на стороне провайдера, так как именно там вы уже управляете пользователями.

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

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

[Service]
User=oauth2-proxy
Group=oauth2-proxy
ExecStart=/usr/local/bin/oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg
Restart=on-failure
ProtectSystem=strict
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
sudo useradd --system --no-create-home --shell /usr/sbin/nologin oauth2-proxy
sudo install -d -m 750 /etc/oauth2-proxy
sudo chown -R oauth2-proxy:oauth2-proxy /etc/oauth2-proxy
sudo chmod 600 /etc/oauth2-proxy/oauth2-proxy.cfg
sudo systemctl daemon-reload
sudo systemctl enable --now oauth2-proxy
curl -s http://127.0.0.1:4180/ping

Вывод /ping с OK означает, что процесс запущен и загрузил конфигурацию. Это встроенный health-endpoint сервиса oauth2-proxy, который никогда не запрашивает сессию. Если ответа нет, изучите journalctl -u oauth2-proxy -n 50, так как неверный URL issuer или неверная длина cookie secret приводят к сбою при запуске, о чем сервис сообщает в логах.

Регистрация URI перенаправления у вашего провайдера

Создайте OIDC-приложение у вашего провайдера и установите его URI перенаправления в точном соответствии с redirect_url из конфигурации: https://app.example.com/oauth2/callback. «В точном соответствии» означает, что схема, хост, порт и путь должны совпадать символ в символ. Наличие или отсутствие завершающего слэша делает URI другим.

Это самая частая причина сбоев во всей настройке, и она проявляется еще до того, как в процесс включается oauth2-proxy. Провайдер отклоняет запрос на авторизацию и выводит собственную страницу ошибки, поэтому в логах oauth2-proxy ничего не появляется. Признак проблемы находится в адресной строке: браузер всё ещё находится на домене вашего провайдера, а строка запроса содержит error=invalid_request или прямо указывает на redirect_uri. Если вы видите это, исправляйте запись приложения у провайдера, а не конфигурацию прокси.

Скопируйте URL издателя (issuer URL) из панели провайдера, вместо того чтобы вводить его вручную. oauth2-proxy добавляет /.well-known/openid-configuration к oidc_issuer_url и при запуске запрашивает этот документ обнаружения. Проверьте его самостоятельно:

curl -s https://id.example.com/application/o/myapp/.well-known/openid-configuration | head -c 400

JSON, содержащий ключ authorization_endpoint, означает, что URL издателя указан верно. Ошибка 404 или HTML-страница с ошибкой означают, что URL неверный, и oauth2-proxy не сможет запуститься из-за той же ошибки 404. Если вы еще не выбрали провайдера, сравнение Keycloak, Authentik и Zitadel поможет оценить плюсы и минусы, а руководство по развертыванию Authentik в качестве собственного SSO-сервера подробно описывает настройку провайдера для данного сценария.

Nginx: auth_request

Nginx выполняет предварительную аутентификацию с помощью auth_request, который инициирует внутренний подзапрос и выполняет ветвление в зависимости от его кода состояния.

# in the http context, next to your other maps
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

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

  location /oauth2/ {
    proxy_pass       http://127.0.0.1:4180;
    proxy_set_header Host                    $host;
    proxy_set_header X-Real-IP               $remote_addr;
    proxy_set_header X-Auth-Request-Redirect $request_uri;
  }

  location = /oauth2/auth {
    proxy_pass       http://127.0.0.1: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;
  }

  location / {
    auth_request /oauth2/auth;
    error_page 401 = @oauth2_signin;

    auth_request_set $user  $upstream_http_x_auth_request_user;
    auth_request_set $email $upstream_http_x_auth_request_email;
    proxy_set_header X-User  $user;
    proxy_set_header X-Email $email;

    auth_request_set $auth_cookie $upstream_http_set_cookie;
    add_header Set-Cookie $auth_cookie;

    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host       $host;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }

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

Три детали в этой конфигурации имеют важное значение. proxy_pass_request_body off с пустым значением Content-Length предотвращает копирование Nginx тела каждого POST-запроса в подзапрос, что важно, так как oauth2-proxy не считывает его. При загрузке файла значение по умолчанию привело бы к отправке файла дважды.

Пара auth_request_set $auth_cookie и add_header Set-Cookie передаёт обновлённый cookie сессии обратно в браузер. Если их не указать, cookie_refresh будет молча бездействовать, так как Nginx отбрасывает заголовки Set-Cookie из подзапроса, и браузер сохранит старое значение до истечения срока действия сессии.

error_page 401 = @oauth2_signin превращает неудачную проверку в перенаправление на страницу входа. Без этой директивы неавторизованный посетитель получит пустую страницу 401 Authorization Required без возможности продолжить работу.

Всегда проверяйте конфигурацию перед перезагрузкой:

sudo nginx -t && sudo systemctl reload nginx

Если приведённые директивы вам незнакомы, анатомия конфигурации reverse proxy в nginx описывает базовый уровень, на котором строится эта настройка.

Traefik: middleware forwardAuth

Для выполнения этой задачи Traefik требует два middleware. Первый выполняет проверку. Второй преобразует ответ 401 в редирект браузера.

# dynamic configuration
http:
  middlewares:
    oauth-auth:
      forwardAuth:
        address: https://oauth.example.com/oauth2/auth
        trustForwardHeader: true
    oauth-errors:
      errors:
        status:
          - "401-403"
        service: oauth-backend
        query: "/oauth2/sign_in?rd={url}"
        statusRewrites:
          "401": 302

Подключите оба к роутеру, который стоит перед вашим приложением, и опубликуйте oauth2-proxy на отдельном роутере по адресу oauth.example.com. Это необходимо, так как браузер должен иметь доступ к /oauth2/sign_in и /oauth2/callback, не проходя проверку.

statusRewrites — это отображение кода 401 на 302, которое часто упускают из виду. Без него Traefik возвращает редирект на страницу входа со статусом 401, браузер не переходит по нему, и пользователь видит страницу, содержащую только слово Found..

trustForwardHeader: true передает исходный хост и URI в oauth2-proxy, которому они нужны для формирования значения rd, возвращающего пользователя на запрошенную страницу. Настройте whitelist_domains так, чтобы он охватывал и этот хост, иначе oauth2-proxy отбросит параметр rd как риск открытого редиректа, и после входа все будут попадать на /. Обычно один экземпляр Traefik обслуживает сразу несколько приложений, а в статье маршрутизация нескольких приложений Docker Compose через один экземпляр Traefik показана схема роутера, в которую встраивается данное решение.

Caddy: forward_auth

app.example.com {
  handle /oauth2/* {
    reverse_proxy oauth2-proxy.internal:4180 {
      header_up X-Real-IP {remote_host}
      header_up X-Forwarded-Uri {uri}
    }
  }
  handle {
    forward_auth oauth2-proxy.internal:4180 {
      uri /oauth2/auth
      header_up X-Real-IP {remote_host}
      copy_headers X-Auth-Request-User X-Auth-Request-Email
      @error status 401
      handle_response @error {
        redir * /oauth2/sign_in?rd={scheme}://{host}{uri}
      }
    }
    reverse_proxy upstream.internal:3000
  }
}

Порядок имеет значение. Блок /oauth2/* должен идти первым и не содержать forward_auth, так как неавторизованный пользователь должен иметь доступ к путям входа и обратного вызова. Если разместить проверку перед этими путями, произойдет бесконечный цикл перенаправлений на страницу входа, пока браузер не прервет соединение.

copy_headers передает данные об идентификации в вышестоящий (upstream) запрос; этот механизм работает, только если oauth2-proxy запущен с флагом set_xauthrequest = true. Выбор между прокси-серверами — отдельная задача, и сравнение Nginx, Caddy и Traefik поможет в ней разобраться.

Почему происходит зацикливание на странице входа?

Вы авторизуетесь у провайдера, он перенаправляет вас обратно, а oauth2-proxy сразу отправляет вас к провайдеру снова. Зацикливание означает, что запрос обратного вызова (callback) поступил без cookie, который oauth2-proxy установил при отправке. В логах указана причина:

No cookies were found in OAuth callback.

или, если другой cookie дошел, а нужный — нет:

Cookies were found in OAuth callback, but none was a CSRF cookie.

CSRF — это межсайтовая подделка запроса (cross-site request forgery). Этот cookie нужен, чтобы связать обратный вызов с процессом входа, который его инициировал. В браузере эта ошибка выглядит так:

Login Failed: Unable to find a valid CSRF token. Please try again.

Проверьте эти четыре причины по порядку.

  1. cookie_secure = true при обращении к сайту по обычному HTTP. Браузер не сохранит cookie с флагом Secure для источника http://, поэтому он не будет отправлен обратно. Настройте TLS (transport layer security) должным образом или используйте cookie_secure = false только при тестировании на localhost.
  2. Значение cookie_domains не охватывает имя хоста в адресной строке. .example.com охватывает app.example.com, но никак не влияет на app.example.net.
  3. Браузер отбрасывает cookie. Строгие настройки приватности или блокировщики сторонних cookie могут удалять _oauth2_proxy_csrf между исходящим редиректом и обратным вызовом.
  4. Рассинхронизация времени. Если системное время сервера сильно отличается от времени провайдера, значения iat и exp в ID-токене выходят за пределы допустимого окна, и сессия отклоняется при получении. timedatectl должен сообщать о System clock synchronized: yes.

Вместо догадок отследите процесс на стороне сервера:

sudo journalctl -u oauth2-proxy -f

Откройте приложение в режиме инкогнито. Каждый запрос логируется с указанием статуса, поэтому обратный вызов, за которым сразу следует новый редирект к провайдеру, будет зафиксирован как зацикливание.

Пути, которые должны обходить авторизацию: API, вебхуки и вебсокеты

Forward auth предполагает наличие у браузера cookie. Клиенты без браузера не смогут пройти проверку.

API-клиент, отправляющий Authorization: Bearer <token>, не имеет cookie, поэтому он получает редирект 302 на страницу входа вашего провайдера и затем пытается распарсить HTML как JSON. Есть два корректных решения. Настройка skip_jwt_bearer_tokens = true позволяет oauth2-proxy принимать валидный JWT (JSON web token) bearer-токен от того же эмитента, что подходит, если ваши API-клиенты уже получают токены от провайдера. В противном случае исключите путь:

skip_auth_routes = [
  "^/api/",
  "POST=^/webhook/",
  "GET=^/healthz$"
]

Каждое значение — это регулярное выражение, которое сопоставляется с нормализованным путем, при необходимости с префиксом HTTP-метода и =. POST=^/webhook/ оставляет обработчик вебхука открытым для POST-запросов, в то время как пользователь, переходящий по тому же пути в браузере, всё равно попадает на страницу входа. Каждая такая запись — это брешь в защите, поэтому ограничивайте выражения символом ^ и делайте их максимально узкими, насколько позволяет клиент.

Вебсокеты — это случай, в котором часто ошибаются. Запрос на upgrade — это обычный HTTP GET, содержащий те же cookie, что и любой другой запрос, поэтому он проходит проверку в штатном режиме и не требует исключений. Проблема возникает при проксировании. Без заголовков Upgrade и Connection в защищенной локации процесс upgrade никогда не завершается, и клиент приложения бесконечно повторяет попытки, выводя сообщение WebSocket connection ... failed в консоль браузера. Исключение пути здесь не поможет, так как запрос уже был авторизован.

Существует одно реальное ограничение. Проверка выполняется один раз, в момент upgrade. Вебсокет, который остается открытым часами, не перепроверяется, поэтому удаление пользователя у провайдера не приведет к закрытию уже установленного соединения. Чтобы прервать активные подключения, перезапустите приложение.

По умолчанию вся сессия хранится внутри cookie, зашифрованного с помощью cookie_secret. Это позволяет oauth2-proxy оставаться stateless и не требует дополнительных сервисов. У этого подхода есть ограничение: браузеры ограничивают размер cookie примерно 4 KB. Когда ID token содержит длинный список групп (group claims), oauth2-proxy разбивает сессию на части _oauth2_proxy_0, _oauth2_proxy_1 и так далее. Если частей становится много, заголовки запроса увеличиваются настолько, что nginx возвращает 400 Request Header Or Cookie Too Large еще до того, как приложение получит запрос.

В таких случаях перенесите хранение сессий на сторону сервера:

session_store_type = "redis"
redis_connection_url = "redis://127.0.0.1:6379"

В этом случае браузер хранит только короткий тикет, а зашифрованная сессия находится в Redis. Платой за это становится необходимость поддерживать работу дополнительного сервиса: если Redis выйдет из строя, все сессии станут недействительными, и все пользователи будут принудительно разлогинены. У cookie store есть свой недостаток: два запроса, одновременно обновляющие одну и ту же сессию, могут конфликтовать, что приведет к необходимости повторного входа в систему.

Чего не дает forward auth

Это пропуск на входе. Он не является авторизацией внутри приложения, и эта разница определяет, подходит ли данный подход для вашего случая.

Как только пользователь проходит проверку, приложение видит то же, что и всегда. Если у приложения есть собственные роли, forward auth не заполняет их, если только приложение не поддерживает аутентификацию на основе заголовков и не умеет сопоставлять заголовок с учетной записью. Grafana умеет это делать через настройки auth.proxy. Большинство self-hosted приложений этого не умеют, поэтому для приложения каждый, кто прошел через «ворота», является одной и той же личностью, и эта личность часто обладает правами администратора.

Этот метод также не защищает собственные API-токены приложения. Личный токен доступа, выданный приложением, проходит аутентификацию в приложении, а не в oauth2-proxy, поэтому токен перестает работать, как только перед ним оказывается «шлюз». Исключите путь API из проверки, чтобы вернуть его работоспособность, и этот токен станет единственным средством защиты данного пути. Теперь вы используете две системы аутентификации для одного сервиса, и SSO покрывает только одну из них.

Отзыв доступа — третий пробел. Удаление пользователя у вашего провайдера останавливает новые входы и обновление токена, которое выполняет cookie_refresh, но существующий сессионный cookie остается действительным до истечения срока его действия. cookie_expire по умолчанию составляет 168 часов, что дает неделю доступа тому, кого вы только что удалили. Установите cookie_refresh на меньшее значение, например, на один час, чтобы отзыв доступа произошел в рамках этого окна.

Аудиторский след также обрывается на входе. oauth2-proxy регистрирует, кто и когда прошел через него. Приложение регистрирует безымянную сессию. Если вам нужно ответить на вопрос, кто изменил настройку, идентификация через заголовки в приложении — это минимум, а реальные учетные записи для каждого пользователя — это честный ответ.

Когда оплата SSO-тарифа является лучшим решением

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

Это неподходящий инструмент, если разным пользователям нужны разные права внутри одного приложения. Прокси-шлюз не может реализовать логику вида «Ана может редактировать дашборды, а Бо может только просматривать их». Если вендор предлагает SSO в составе платного тарифа, вы фактически покупаете механизм сопоставления групп и ролей. Попытка воссоздать этот функционал с помощью заголовков и правил проксирования будет менее надежной, чем покупка подписки. Стоит ознакомиться с моделью ценообразования для SSO-тарифов, прежде чем принимать решение.

Есть еще две ситуации, указывающие на необходимость покупки SSO. Во-первых, требования комплаенса, требующие ведения аудита действий каждого пользователя внутри приложения: логи доступа прокси не будут приняты в качестве доказательства. Во-вторых, любые мобильные или десктопные клиенты, которые не поддерживают передачу браузерных cookies, будут конфликтовать с механизмом аутентификации при каждом запросе.

FAQ

Что такое forward auth?

Forward auth — это шаблон, при котором обратный прокси-сервер запрашивает у отдельного сервиса аутентификации разрешение для каждого входящего запроса перед тем, как передать его дальше. Прокси отправляет заголовки запроса на эндпоинт, такой как /oauth2/auth, и считывает код ответа. Код 202 означает разрешение, поэтому исходный запрос передаётся приложению. Код 401 означает отсутствие сессии, поэтому прокси перенаправляет браузер на страницу входа. Nginx реализует это с помощью директивы auth_request, Traefik — через middleware forwardAuth, а Caddy — с помощью forward_auth.

Почему oauth2-proxy перенаправляет меня на страницу входа в бесконечном цикле?

Запрос обратного вызова (callback) поступил в oauth2-proxy без CSRF-куки, поэтому oauth2-proxy начинает процесс заново. В логе сервера отображается No cookies were found in OAuth callback., а в браузере — Login Failed: Unable to find a valid CSRF token. Please try again.. Чаще всего причина кроется в cookie_secure = true на сайте, к которому браузер обратился по обычному HTTP, так как браузер не сохранит куку Secure для источника http://. Следующая по частоте причина — значение cookie_domains, которое не охватывает имя хоста в адресной строке.

Как пропустить API-клиент или вебхук через oauth2-proxy?

Используйте skip_auth_routes с регулярным выражением, привязанным к началу строки, при необходимости ограничив его одним HTTP-методом, например POST=^/webhook/. Если ваши API-клиенты уже имеют JWT, выданные тем же провайдером, skip_jwt_bearer_tokens = true примет эти токены вместо куки и сохранит защиту пути. Всё, что указано в skip_auth_routes, доступно без аутентификации для всех, поэтому делайте каждое выражение максимально узким, насколько это позволяет вызывающая сторона.

Предоставляет ли oauth2-proxy приложению права доступа для конкретных пользователей?

Нет. Это шлюз, а не система авторизации. Он решает, кто получит доступ к приложению, но для самого приложения все пользователи выглядят одинаково, если только оно не считывает заголовки идентификации и не сопоставляет их с учётными записями. Grafana умеет это делать через настройки auth.proxy. Большинство self-hosted приложений не обладают такой функцией, поэтому каждый человек, прошедший через шлюз, использует ту единственную учётную запись, под которой запущено приложение.

Может ли кто-то обойти oauth2-proxy, самостоятельно установив заголовок идентификации?

Да, если у него есть прямой доступ к приложению. Идентификационные данные приходят в виде обычного заголовка, например X-Auth-Request-Email, и приложение доверяет всему, что получает. Любой, кто может установить соединение с портом приложения, может отправить этот заголовок и представиться любым пользователем. Привязывайте приложение к 127.0.0.1 или держите его во внутренней сети Docker без опубликованных портов, проверяя это с помощью sudo ss -tlnp.

#oauth2-proxy#sso#oidc#reverse-proxy#forward-auth