oauth2-proxy: jak dodać SSO do aplikacji bez logowania
Dowiedz się, jak wdrożyć mechanizm forward auth przy użyciu oauth2-proxy. Skonfiguruj Nginx lub Traefik, aby uniknąć błędów z ciasteczkami i poprawnie obsłużyć sesje OIDC.
Forward auth: jak zapewnić SSO aplikacji bez własnego logowania
oauth2-proxy umożliwia korzystanie z mechanizmu single sign-on w aplikacjach, które nie posiadają własnego systemu logowania. Działa to w ten sposób, że reverse proxy znajdujące się przed aplikacją wstrzymuje każde żądanie, pyta oauth2-proxy o ważność sesji i przekazuje żądanie do backendu tylko w przypadku pozytywnej odpowiedzi. Kod aplikacji pozostaje niezmieniony, ponieważ aplikacja nie bierze udziału w procesie weryfikacji.
Weryfikacja polega na wykonaniu dodatkowego żądania HTTP. Proxy przesyła kopię nagłówków przychodzącego żądania do /oauth2/auth i odczytuje kod statusu. Kod 202 oznacza, że użytkownik posiada aktywną sesję, więc proxy przekazuje oryginalne żądanie do aplikacji. Kod 401 oznacza brak sesji, co powoduje przekierowanie przeglądarki do /oauth2/sign_in, gdzie rozpoczyna się proces logowania OpenID Connect (OIDC) u dostawcy tożsamości. OIDC to warstwa tożsamości zbudowana na bazie OAuth 2.0, a dostawcą jest dowolne rozwiązanie używane w danej infrastrukturze do obsługi logowania.
Ten wzorzec posiada specyficzną nazwę w każdym reverse proxy. Nginx używa dyrektywy auth_request. Traefik określa to mianem middleware forwardAuth. Caddy realizuje to za pomocą forward_auth. Usługa odpowiadająca na podzapytania jest również wymienna. oauth2-proxy jest najczęściej wybieranym rozwiązaniem, ponieważ obsługuje standard OIDC i nie wymaga własnej bazy danych.
Wyznacz granicę zaufania przed przystąpieniem do konfiguracji
Po pomyślnej weryfikacji oauth2-proxy zwraca tożsamość w nagłówkach odpowiedzi, a reverse proxy kopiuje je do żądania upstream. Przy włączonym set_xauthrequest otrzymujesz X-Auth-Request-User oraz X-Auth-Request-Email. Aplikacja odczytuje te nagłówki i uznaje je za wiarygodne.
To cały model bezpieczeństwa, dlatego należy jasno określić jego konsekwencje. Każdy podmiot, który może nawiązać połączenie TCP z portem aplikacji, może samodzielnie ustawić te nagłówki i podszyć się pod dowolnego użytkownika. Pojedynczy curl -H "X-Auth-Request-Email: admin@example.com" http://app-host:3000/ stanowi całkowite obejście zabezpieczeń, jeśli dotrze bezpośrednio do aplikacji.
Z tego powodu aplikacja nie może być dostępna inaczej niż przez proxy. W Docker Compose należy usunąć mapowanie ports: z usługi aplikacji i pozostawić ją w sieci wewnętrznej, aby tylko kontener proxy mógł się z nią połączyć. Na serwerze typu bare host należy powiązać aplikację z adresem 127.0.0.1:3000 zamiast 0.0.0.0:3000. Następnie należy sprawdzić, co faktycznie zostało wystawione:
sudo ss -tlnp | grep 3000Linia zawierająca 0.0.0.0:3000 oznacza, że aplikacja odpowiada na publicznym adresie IP, a brama jest jedynie dekoracją. 127.0.0.1:3000 to stan pożądany. Reguła firewalla stanowi użyteczną drugą warstwę ochrony, jednak adres powiązania jest tym elementem, który pozostaje skuteczny nawet po zresetowaniu zestawu reguł przez inne narzędzie.
Instalacja oauth2-proxy
Według stanu na sierpień 2026 r. aktualnym wydaniem jest v7.15.3, opublikowane w czerwcu 2026 r. Należy zainstalować plik binarny i zweryfikować pobrany plik:
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 --versionsha256sum -c musi wyświetlić linię kończącą się ciągiem OK. Jeśli wyświetlony zostanie ciąg FAILED, należy przerwać proces i pobrać plik ponownie, zamiast uruchamiać plik binarny.
W środowisku Docker obrazem jest quay.io/oauth2-proxy/oauth2-proxy i należy przypiąć konkretną wersję tagiem: quay.io/oauth2-proxy/oauth2-proxy:v7.15.3. Pozostawienie tagu latest zmienia rutynowy proces docker compose pull w nieplanowaną aktualizację procesu zabezpieczającego wszystkie aplikacje na serwerze.
Generowanie klucza tajnego ciasteczka
Ciasteczko sesyjne jest szyfrowane, a cookie_secret stanowi klucz. Musi on mieć dokładnie 16, 24 lub 32 bajty, ponieważ staje się kluczem AES (Advanced Encryption Standard). Każda inna długość spowoduje, że oauth2-proxy odmówi uruchomienia, zgłaszając błąd startowy wskazujący na klucz tajny ciasteczka.
openssl rand -base64 32 | tr -- '+/' '-_'Użycie tr nie jest zabiegiem kosmetycznym. Konwertuje ono standardowe base64 na alfabet bezpieczny dla adresów URL, dzięki czemu wartość ta jest poprawnie interpretowana przez powłokę, plik środowiskowy oraz nagłówek HTTP bez problemów z cytowaniem.
Obowiązują dwie zasady dotyczące tej wartości. Należy używać unikalnego klucza dla każdego wdrożenia. W przypadku uruchamiania więcej niż jednej instancji oauth2-proxy w ramach tej samej domeny, należy przypisać im ten sam klucz, ponieważ ciasteczko zaszyfrowane przez jedną instancję musi być możliwe do odczytania przez pozostałe.
Konfiguracja oauth2-proxy
Przechowywanie ustawień w pliku, zamiast w długiej linii poleceń, zapobiega wyświetlaniu client secret w danych wyjściowych 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 instruuje oauth2-proxy, aby ufało nagłówkom X-Forwarded-* pochodzącym z poprzedzającego go proxy. Bez tego ustawienia oauth2-proxy traktuje adres proxy jako adres klienta, co może prowadzić do błędnej oceny, czy żądanie dotarło przez HTTPS.
upstreams = ["static://202"] sprawia, że oauth2-proxy odpowiada kodem 202 na uwierzytelnione żądanie i nie przekazuje dalej treści, co jest wymagane w przypadku forward auth, ponieważ to reverse proxy odpowiada za przekazywanie ruchu. Inny model wdrożenia zakłada umieszczenie oauth2-proxy bezpośrednio w ścieżce żądania za pomocą upstreams = ["http://127.0.0.1:3000"], bez użycia auth_request. Jest to prostsze rozwiązanie dla pojedynczej aplikacji, ale nie skaluje się do dziesięciu.
email_domains = ["*"] dopuszcza każdy adres, który zostanie uwierzytelniony przez dostawcę. Należy ograniczyć to ustawienie do własnej domeny lub, co jest lepszym rozwiązaniem, ograniczyć dostęp za pomocą przypisania do grupy po stronie dostawcy, ponieważ to tam zarządzane są konta użytkowników.
Uruchom usługę w systemd jako dedykowany użytkownik:
# /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.targetsudo 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 wyświetlające OK oznacza, że proces został uruchomiony i wczytał konfigurację. Jest to wewnętrzny punkt kontroli stanu (health endpoint) oauth2-proxy, który nie wymaga sesji. Jeśli brak jest odpowiedzi, należy sprawdzić journalctl -u oauth2-proxy -n 50, ponieważ błędny adres URL wystawcy (issuer URL) lub nieprawidłowa długość klucza ciasteczek (cookie secret) powodują błąd przy starcie, o czym proces informuje w logach.
Rejestracja URI przekierowania u dostawcy
Utwórz aplikację OIDC u swojego dostawcy i ustaw jej URI przekierowania dokładnie na wartość redirect_url z konfiguracji: https://app.example.com/oauth2/callback. Dokładność oznacza, że schemat, host, port oraz ścieżka muszą być identyczne znak w znak. Końcowy ukośnik sprawia, że URI jest traktowany jako inny adres.
Jest to najczęstsza przyczyna niepowodzeń w całej konfiguracji, a błąd występuje jeszcze przed zaangażowaniem oauth2-proxy. Dostawca odrzuca żądanie autoryzacji i wyświetla własną stronę błędu, dlatego w dzienniku oauth2-proxy nie pojawiają się żadne wpisy. Wskazówką jest pasek adresu: przeglądarka nadal znajduje się w domenie dostawcy, a ciąg zapytania zawiera error=invalid_request lub bezpośrednio nazwy stron redirect_uri. W takiej sytuacji należy poprawić wpis aplikacji u dostawcy, a nie w konfiguracji proxy.
Skopiuj adres URL wystawcy (issuer URL) od dostawcy zamiast wpisywać go ręcznie. oauth2-proxy dodaje /.well-known/openid-configuration do oidc_issuer_url i pobiera dokument discovery podczas uruchamiania. Sprawdź go samodzielnie w pierwszej kolejności:
curl -s https://id.example.com/application/o/myapp/.well-known/openid-configuration | head -c 400Plik JSON zawierający klucz authorization_endpoint oznacza, że adres URL wystawcy jest poprawny. Błąd 404 lub strona błędu w formacie HTML oznacza, że adres jest błędny, a oauth2-proxy nie uruchomi się z powodu tego samego błędu 404. Jeśli jeszcze nie wybrano dostawcy, porównanie Keycloak, Authentik i Zitadel omawia wady i zalety tych rozwiązań, a uruchomienie Authentik jako własnego serwera SSO przeprowadza przez proces konfiguracji strony dostawcy dla tego konkretnego scenariusza.
Nginx: auth_request
Nginx realizuje uwierzytelnianie przekazywane za pomocą auth_request, co wyzwala wewnętrzne podzapytanie i rozgałęzia działanie w zależności od otrzymanego kodu statusu.
# 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;
}
}Trzy szczegóły w tym rozwiązaniu mają kluczowe znaczenie. Użycie proxy_pass_request_body off z pustym Content-Length zapobiega kopiowaniu przez Nginx treści każdego żądania POST do podzapytania, co jest istotne, ponieważ oauth2-proxy nie przetwarza tych danych. W przypadku przesyłania plików, ustawienie domyślne powoduje dwukrotne wysłanie pliku.
Para auth_request_set $auth_cookie oraz add_header Set-Cookie przekazuje odświeżony plik cookie sesji z powrotem do przeglądarki. Pominięcie tych dyrektyw sprawia, że cookie_refresh nie wywołuje żadnego efektu, ponieważ Nginx odrzuca nagłówki Set-Cookie z podzapytania, a przeglądarka zachowuje starą wartość do momentu wygaśnięcia sesji.
error_page 401 = @oauth2_signin odpowiada za przekierowanie nieudanego sprawdzenia do strony logowania. Bez tego elementu nieuwierzytelniony użytkownik otrzymuje jedynie surową stronę 401 Authorization Required i nie ma możliwości kontynuowania pracy.
Zawsze wykonuj test przed przeładowaniem konfiguracji:
sudo nginx -t && sudo systemctl reload nginxJeśli otaczające dyrektywy są nieznane, anatomia konfiguracji reverse proxy w nginx opisuje warstwę znajdującą się poniżej.
Traefik: middleware forwardAuth
Traefik wymaga dwóch middleware do wykonania tego zadania. Pierwszy przeprowadza weryfikację. Drugi zamienia kod 401 na przekierowanie w przeglądarce.
# 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": 302Podepnij oba do routera obsługującego aplikację i opublikuj oauth2-proxy na własnym routerze pod adresem oauth.example.com, ponieważ przeglądarka musi uzyskać dostęp do /oauth2/sign_in oraz /oauth2/callback bez przechodzenia przez weryfikację.
statusRewrites mapowanie 401 na 302 to element, który jest często pomijany. Bez tego Traefik zwraca przekierowanie do logowania ze statusem 401, przeglądarka go nie wykonuje, a użytkownik widzi stronę zawierającą tylko słowo Found.
trustForwardHeader: true przekazuje oryginalny host oraz URI do oauth2-proxy, które potrzebuje ich do zbudowania wartości rd, kierującej użytkownika z powrotem na żądaną stronę. Skonfiguruj whitelist_domains tak, aby obejmowało również ten host, w przeciwnym razie oauth2-proxy odrzuci parametr rd jako ryzyko otwartego przekierowania (open-redirect), a wszyscy użytkownicy po zalogowaniu trafią na /. Serwer Traefik zazwyczaj obsługuje jednocześnie kilka aplikacji, a kierowanie ruchu wielu aplikacji Docker Compose przez jedną instancję Traefik przedstawia układ routerów, w którym można to zaimplementować.
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
}
}Kolejność ma tutaj znaczenie. Blok /oauth2/* znajduje się na początku i nie zawiera forward_auth, ponieważ użytkownik niezalogowany musi mieć dostęp do ścieżek logowania oraz callback. Umieszczenie weryfikacji przed tymi ścieżkami spowoduje, że proces logowania będzie przekierowywał w nieskończoność, aż przeglądarka przerwie połączenie.
copy_headers odpowiada za przekazanie tożsamości do żądania upstream i generuje wartości tylko wtedy, gdy oauth2-proxy działa z flagą set_xauthrequest = true. Wybór odpowiedniego proxy to osobne zagadnienie, które omawia porównanie Nginx, Caddy i Traefik.
Dlaczego logowanie zapętla się i wraca do strony logowania?
Użytkownik loguje się u dostawcy, ten odsyła go z powrotem, a oauth2-proxy natychmiast przekierowuje go ponownie do dostawcy. Pętla oznacza, że żądanie zwrotne (callback) dotarło bez pliku cookie, który oauth2-proxy ustawiło przy wysyłaniu żądania. Dziennik wskazuje przyczynę:
No cookies were found in OAuth callback.lub, gdy dotarł inny plik cookie, ale nie ten właściwy:
Cookies were found in OAuth callback, but none was a CSRF cookie.CSRF to cross-site request forgery; plik cookie służy do powiązania żądania zwrotnego z logowaniem, które je zainicjowało. Przeglądarka zgłasza ten sam błąd jako:
Login Failed: Unable to find a valid CSRF token. Please try again.Sprawdź kolejno te cztery przyczyny.
cookie_secure = truepodczas gdy przeglądarka uzyskała dostęp do witryny przez zwykłe HTTP. Przeglądarka nie zapisze pliku cookie oznaczonegoSecuredla źródłahttp://, więc nie zostanie on odesłany. Zakończ TLS (transport layer security) poprawnie lub ustawcookie_secure = falsetylko podczas testów na localhost.- Wartość
cookie_domains, która nie obejmuje nazwy hosta w pasku adresu..example.comobejmujeapp.example.comi nie działa w ogóle dlaapp.example.net. - Przeglądarka odrzuca plik cookie. Restrykcyjne rozszerzenie chroniące prywatność lub blokowanie plików cookie stron trzecich może usuwać
_oauth2_proxy_csrfmiędzy wychodzącym przekierowaniem a żądaniem zwrotnym. - Rozbieżność czasu (clock drift). Jeśli zegar serwera znacznie różni się od zegara dostawcy, wartości
iatorazexptokena ID wykraczają poza akceptowalne okno czasowe i sesja jest odrzucana przy próbie nawiązania.timedatectlpowinno zgłosićSystem clock synchronized: yes.
Monitoruj zdarzenie po stronie serwera zamiast zgadywać:
sudo journalctl -u oauth2-proxy -fZaładuj aplikację w oknie prywatnym. Każde żądanie jest rejestrowane wraz ze statusem, więc żądanie zwrotne, po którym natychmiast następuje kolejne przekierowanie do dostawcy, jest udokumentowaną pętlą.
Ścieżki, które muszą pomijać logowanie: API, webhooks i websockets
Forward auth zakłada, że przeglądarka przechowuje plik cookie. Wywołania bez przeglądarki kończą się niepowodzeniem.
Klient API wysyłający Authorization: Bearer <token> nie posiada pliku cookie, więc otrzymuje przekierowanie 302 na stronę logowania dostawcy, a następnie próbuje sparsować kod HTML jako JSON. Istnieją dwa poprawne rozwiązania. Ustawienie skip_jwt_bearer_tokens = true sprawia, że oauth2-proxy akceptuje poprawny token bearer JWT (JSON web token) od tego samego wystawcy, co jest właściwym rozwiązaniem, gdy klienci API już pobierają tokeny od dostawcy. W przeciwnym razie należy wykluczyć ścieżkę:
skip_auth_routes = [
"^/api/",
"POST=^/webhook/",
"GET=^/healthz$"
]Każda wartość to wyrażenie regularne dopasowywane do znormalizowanej ścieżki, opcjonalnie poprzedzone metodą HTTP i =. POST=^/webhook/ pozostawia odbiornik webhook otwarty dla metody POST, podczas gdy użytkownik przeglądający tę samą ścieżkę nadal trafia na stronę logowania. Każdy wpis stanowi lukę w zabezpieczeniach, dlatego należy ograniczyć wyrażenia za pomocą ^ i zachować je tak wąskimi, jak pozwala na to klient.
Websockety to przypadek, w którym często popełniane są błędy. Żądanie upgrade to zwykłe HTTP GET, przenoszące te same pliki cookie co każde inne żądanie, więc przechodzi ono weryfikację standardowo i nie wymaga wykluczenia. Problemem jest proxying wokół niego. Bez nagłówków Upgrade i Connection w chronionej lokalizacji, upgrade nigdy nie zostaje ukończony, a klient aplikacji ponawia próby w nieskończoność z komunikatem WebSocket connection ... failed w konsoli przeglądarki. Wykluczenie ścieżki w tym przypadku nic nie daje, ponieważ żądanie zostało już autoryzowane.
Obowiązuje jedno realne ograniczenie. Weryfikacja jest uruchamiana jednorazowo, w momencie upgrade. Websocket, który pozostaje otwarty przez wiele godzin, nie jest ponownie sprawdzany, więc usunięcie użytkownika u dostawcy nie powoduje zamknięcia już istniejącego połączenia. Aby przerwać aktywne połączenia, należy zrestartować aplikację.
Przechowywanie sesji i problem zbyt dużych plików cookie
Domyślnie cała sesja przechowywana jest wewnątrz pliku cookie, zaszyfrowanego za pomocą cookie_secret. Dzięki temu oauth2-proxy pozostaje bezstanowe i nie wymaga dodatkowej usługi. Rozwiązanie to ma jednak ograniczenie, ponieważ przeglądarki ograniczają rozmiar pliku cookie do około 4 KB. Gdy token ID zawiera długą listę oświadczeń (claims) dotyczących grup, oauth2-proxy dzieli sesję na _oauth2_proxy_0, _oauth2_proxy_1 i kolejne części. Po przekroczeniu kilku fragmentów nagłówki żądania stają się na tyle duże, że nginx zwraca błąd 400 Request Header Or Cookie Too Large, zanim aplikacja otrzyma żądanie.
W takiej sytuacji należy przenieść przechowywanie sesji po stronie serwera:
session_store_type = "redis"
redis_connection_url = "redis://127.0.0.1:6379"Przeglądarka przechowuje wówczas jedynie krótki bilet (ticket), a zaszyfrowana sesja znajduje się w Redis. Kosztem tego rozwiązania jest konieczność utrzymywania dodatkowej usługi: jeśli Redis przestanie działać, wszystkie sesje staną się nieważne i wszyscy użytkownicy zostaną jednocześnie wylogowani. Przechowywanie w plikach cookie wiąże się z innym problemem: dwa żądania odświeżające tę samą sesję w tym samym momencie mogą wywołać konflikt i wymusić ponowne logowanie.
Czego nie zapewnia forward auth
Jest to mechanizm kontroli dostępu na poziomie wejścia. Nie stanowi on autoryzacji wewnątrz samej aplikacji, a ta różnica decyduje o tym, czy dane podejście jest odpowiednie dla danego przypadku.
Gdy użytkownik przejdzie przez bramkę, aplikacja widzi to samo, co zawsze. Jeśli aplikacja posiada własny system ról, forward auth nie uzupełnia ich, chyba że aplikacja wspiera uwierzytelnianie oparte na nagłówkach i potrafi przypisać nagłówek do konta. Grafana oferuje taką możliwość poprzez ustawienia auth.proxy. Większość aplikacji self-hosted tego nie robi, więc każda osoba, która przejdzie przez bramkę, jest dla aplikacji tą samą tożsamością, często z uprawnieniami administratora.
Mechanizm ten nie chroni również własnych tokenów API aplikacji. Osobisty token dostępu wydany przez aplikację uwierzytelnia użytkownika w aplikacji, a nie w oauth2-proxy, więc token przestaje działać w momencie umieszczenia bramki przed usługą. Wyłączenie ścieżki API spod kontroli przywraca jej działanie, ale wtedy token staje się jedynym zabezpieczeniem tej ścieżki. W efekcie uruchamiane są dwa systemy uwierzytelniania dla jednej usługi, z których tylko jeden jest objęty SSO.
Trzecią luką jest unieważnianie sesji. Usunięcie użytkownika u dostawcy tożsamości blokuje nowe logowania oraz odświeżanie tokenów wykonywane przez cookie_refresh, jednak istniejący plik cookie sesji pozostaje ważny do momentu wygaśnięcia. Domyślna wartość cookie_expire wynosi 168 godzin, co oznacza tydzień dostępu dla osoby, której uprawnienia właśnie odebrano. Należy ustawić cookie_refresh na krótszy czas, na przykład godzinę, aby unieważnienie dostępu nastąpiło w tym oknie czasowym.
Ścieżka audytu również kończy się na bramce. oauth2-proxy rejestruje, kto i kiedy uzyskał dostęp. Aplikacja rejestruje jedynie anonimową sesję. Jeśli konieczne jest ustalenie, kto zmienił ustawienia, minimalnym rozwiązaniem jest przekazywanie tożsamości w nagłówkach, natomiast rzetelnym rozwiązaniem są indywidualne konta użytkowników wewnątrz aplikacji.
Kiedy opłacenie składki SSO jest lepszym rozwiązaniem
Forward auth jest właściwym narzędziem, gdy aplikacja nie posiada mechanizmu logowania lub korzysta z jednego współdzielonego hasła, a wymagane jest centralne zarządzanie dostępem użytkowników. Implementacja zajmuje jedno popołudnie i wymaga uruchomienia dodatkowego procesu, jednak rozwiązanie to współpracuje z każdą aplikacją obsługującą protokół HTTP.
Jest to niewłaściwe narzędzie, gdy różni użytkownicy wymagają różnych uprawnień w obrębie tej samej aplikacji. Mechanizm bramy nie pozwala na wyrażenie reguł typu „Ana może edytować dashboardy, a Bo może je tylko przeglądać”. Jeśli dostawca oferuje płatny plan SSO, zazwyczaj kupuje się właśnie możliwość mapowania grup na role. Próba odtworzenia tego mechanizmu za pomocą nagłówków i reguł proxy jest bardziej podatna na awarie niż wykupienie subskrypcji. Warto zapoznać się z artykułem The pricing pattern behind those SSO tiers przed podjęciem ostatecznej decyzji.
Dwie inne sytuacje wskazują na to samo rozwiązanie. Wymogi zgodności (compliance), wymagające prowadzenia dzienników audytu dla każdego użytkownika wewnątrz aplikacji, nie uznają logów dostępu proxy za wystarczający dowód. Ponadto każda aplikacja z klientem mobilnym lub desktopowym, który nie obsługuje ciasteczek przeglądarkowych, będzie generować błędy przy każdym żądaniu przechodzącym przez bramę.
FAQ
Czym jest forward auth?
Forward auth to wzorzec, w którym reverse proxy pyta oddzielną usługę uwierzytelniania o każde przychodzące żądanie przed przekazaniem go do backendu. Proxy wysyła nagłówki żądania do punktu końcowego, takiego jak /oauth2/auth, i odczytuje kod statusu. Kod 202 oznacza zezwolenie, więc oryginalne żądanie trafia do aplikacji. Kod 401 oznacza brak sesji, więc proxy przekierowuje przeglądarkę do strony logowania. Nginx implementuje to za pomocą dyrektywy auth_request, Traefik za pomocą middleware forwardAuth, a Caddy za pomocą forward_auth.
Dlaczego oauth2-proxy zapętla przekierowania do strony logowania?
Żądanie zwrotne (callback) dotarło do oauth2-proxy bez pliku cookie CSRF, więc oauth2-proxy rozpoczyna proces od nowa. Dziennik serwera zawiera No cookies were found in OAuth callback., a przeglądarka wyświetla Login Failed: Unable to find a valid CSRF token. Please try again.. Najczęstszą przyczyną jest cookie_secure = true w witrynie, do której przeglądarka uzyskała dostęp przez zwykłe HTTP, ponieważ przeglądarka nie zapisze pliku cookie Secure dla źródła http://. Kolejną częstą przyczyną jest wartość cookie_domains, która nie obejmuje nazwy hosta widocznej w pasku adresu.
Jak przepuścić klienta API lub webhook przez oauth2-proxy?
Należy użyć skip_auth_routes z zakotwiczonym wyrażeniem regularnym, opcjonalnie ograniczonym do jednej metody HTTP, na przykład POST=^/webhook/. Jeśli klienci API posiadają już tokeny JWT wydane przez tego samego dostawcę, skip_jwt_bearer_tokens = true zaakceptuje te tokeny zamiast pliku cookie i utrzyma ochronę ścieżki. Wszystko wymienione w skip_auth_routes jest nieuwierzytelnione dla każdego, więc należy ograniczyć każde wyrażenie tak bardzo, jak pozwala na to klient.
Czy oauth2-proxy nadaje aplikacji uprawnienia dla poszczególnych użytkowników?
Nie. Jest to brama, a nie system autoryzacji. Decyduje o tym, kto uzyska dostęp do aplikacji, a każdy, kto go uzyska, wygląda dla aplikacji identycznie, chyba że aplikacja odczytuje nagłówki tożsamości i mapuje je na konta. Grafana może to robić poprzez ustawienia auth.proxy. Większość aplikacji self-hosted nie posiada takiej funkcji, więc każda osoba za bramą współdzieli tę samą tożsamość, z którą uruchomiona jest aplikacja.
Czy ktoś może ominąć oauth2-proxy, samodzielnie ustawiając nagłówek tożsamości?
Tak, jeśli może połączyć się bezpośrednio z aplikacją. Tożsamość dociera jako zwykły nagłówek, taki jak X-Auth-Request-Email, a aplikacja ufa wszystkiemu, co otrzyma. Każdy, kto może otworzyć połączenie z portem aplikacji, może wysłać ten nagłówek i podszyć się pod dowolnego użytkownika. Należy powiązać aplikację z 127.0.0.1 lub utrzymać ją w wewnętrznej sieci Docker bez wystawionego portu i potwierdzić to za pomocą sudo ss -tlnp.