SSD Nodes Learn 🎉 VPS từ $5.50/tháng
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-21

Cấu hình SSO cho app không có đăng nhập với oauth2-proxy

Dùng forward auth để đặt app không có login sau OIDC provider. Cấu hình oauth2-proxy với nginx hoặc Traefik, tránh lỗi cookie và vòng lặp redirect.

Xác thực chuyển tiếp: cách cấp SSO cho ứng dụng không có đăng nhập

oauth2-proxy cung cấp single sign-on cho một ứng dụng không có cơ chế đăng nhập riêng. Cơ chế này hoạt động vì reverse proxy phía trước ứng dụng chặn mọi request, hỏi oauth2-proxy xem request có session hợp lệ hay không, rồi chỉ chuyển request đến upstream khi nhận được câu trả lời có. Code của ứng dụng không cần thay đổi vì ứng dụng không bao giờ nhìn thấy bước kiểm tra này.

Bước kiểm tra tạo thêm một HTTP request. Proxy gửi bản sao các request header nhận được đến /oauth2/auth rồi đọc status code. Mã 202 nghĩa là client có session, nên proxy chuyển request gốc đến app. Mã 401 nghĩa là chưa có session, nên proxy chuyển browser đến /oauth2/sign_in. Thành phần này khởi động quy trình đăng nhập OpenID Connect (OIDC) tại identity provider của bạn. OIDC là lớp identity xây dựng trên OAuth 2.0. Provider có thể là hệ thống bạn đang dùng để đăng nhập.

Mẫu cấu hình này có tên riêng trong từng reverse proxy. Nginx gọi directive này là auth_request. Traefik gọi middleware là forwardAuth. Caddy viết là forward_auth. Service trả lời sub-request cũng có thể thay thế. oauth2-proxy là lựa chọn phổ biến vì nó hỗ trợ OIDC thuần và không cần database riêng.

Vẽ ranh giới tin cậy trước khi viết bất kỳ cấu hình nào

Sau khi kiểm tra thành công, oauth2-proxy trả về danh tính trong các response header, rồi reverse proxy sao chép chúng vào request gửi đến upstream. Khi bật set_xauthrequest, bạn nhận được X-Auth-Request-UserX-Auth-Request-Email. Ứng dụng đọc các header đó và tin tưởng chúng.

Đó là toàn bộ mô hình bảo mật, vì vậy hãy nói rõ hệ quả. Bất kỳ thành phần nào có thể mở kết nối TCP đến cổng của ứng dụng đều có thể tự đặt các header đó và giả danh bất kỳ user nào. Chỉ một curl -H "X-Auth-Request-Email: admin@example.com" http://app-host:3000/ cũng đủ bypass hoàn toàn nếu request đến trực tiếp ứng dụng.

Vì vậy, ứng dụng không được phép truy cập theo cách nào khác ngoài thông qua proxy. Trong Docker Compose, hãy xóa mapping ports: khỏi app service và để app trên internal network, để chỉ proxy container mới có thể kết nối đến app. Trên bare host, hãy bind app vào 127.0.0.1:3000 thay vì 0.0.0.0:3000. Sau đó kiểm tra chính xác những gì bạn đã expose:

sudo ss -tlnp | grep 3000

Một dòng có nội dung 0.0.0.0:3000 nghĩa là app đang trả lời trên public IP và lớp kiểm soát của bạn chỉ để trang trí. 127.0.0.1:3000 mới là cấu hình bạn cần. Firewall rule là một lớp bảo vệ bổ sung hữu ích, nhưng bind address mới là thứ vẫn có hiệu lực khi một tool khác xóa toàn bộ ruleset của bạn.

Cài đặt oauth2-proxy

Tính đến tháng 8 năm 2026, bản phát hành hiện tại là v7.15.3, được phát hành vào tháng 6 năm 2026. Cài đặt binary và xác minh file đã tải xuống:

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 phải in ra một dòng kết thúc bằng OK. Nếu in ra FAILED, hãy dừng lại và tải xuống lại thay vì chạy binary.

Trong Docker, image là quay.io/oauth2-proxy/oauth2-proxy, và bạn nên pin tag: quay.io/oauth2-proxy/oauth2-proxy:v7.15.3. Để image ở latest sẽ biến một docker compose pull thường lệ thành việc nâng cấp ngoài kế hoạch cho process đang bảo vệ mọi app trên máy.

Session cookie được mã hóa, và cookie_secret là key. Key phải có đúng 16, 24 hoặc 32 byte vì nó được dùng làm key AES (advanced encryption standard). Với mọi độ dài khác, oauth2-proxy sẽ từ chối khởi động và hiển thị lỗi khởi động có nêu tên cookie secret.

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

tr không phải là tùy chọn mang tính hình thức. Nó chuyển base64 chuẩn sang alphabet an toàn cho URL, để giá trị này không gặp vấn đề về quoting khi đi qua shell, env file và HTTP header.

Có 2 quy tắc cho giá trị này. Dùng secret khác nhau cho từng deployment. Nếu chạy nhiều instance oauth2-proxy phía sau cùng một domain, phải dùng cùng một secret cho tất cả instance, vì cookie được một instance mã hóa phải có thể được các instance khác đọc.

Viết file cấu hình oauth2-proxy

Lưu các thiết lập trong một file thay vì dùng command line dài, để client secret không xuất hiện trong output của 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 yêu cầu oauth2-proxy tin cậy các header X-Forwarded-* do proxy phía trước gửi. Nếu không có thiết lập này, oauth2-proxy sẽ coi địa chỉ của proxy là địa chỉ client và có thể xác định sai request có đi qua HTTPS hay không.

upstreams = ["static://202"] khiến oauth2-proxy trả về mã 202 cho request đã xác thực và không proxy request nào. Đây chính xác là cơ chế forward auth cần, vì reverse proxy mới thực hiện việc proxy. Kiểu triển khai còn lại đặt oauth2-proxy trực tiếp trên đường đi của request với upstreams = ["http://127.0.0.1:3000"] và hoàn toàn không dùng auth_request. Cách này đơn giản hơn cho một app nhưng không mở rộng tốt lên 10 app.

email_domains = ["*"] cho phép mọi địa chỉ mà provider của bạn xác thực. Hãy giới hạn vào domain của bạn, hoặc tốt hơn là giới hạn quyền truy cập bằng group binding trong provider, vì đây là nơi bạn đã quản lý người dùng.

Chạy oauth2-proxy dưới systemd bằng một user riêng:

# /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

Việc /ping in ra OK cho biết process đã khởi động và nạp cấu hình. Đây là health endpoint của oauth2-proxy và endpoint này không bao giờ yêu cầu session. Nếu không có phản hồi, hãy đọc journalctl -u oauth2-proxy -n 50, vì issuer URL sai và cookie secret không đúng độ dài đều khiến process fail khi khởi động, và log đều sẽ nêu rõ nguyên nhân.

Đăng ký redirect URI với provider

Tạo một ứng dụng OIDC trong provider và đặt redirect URI chính xác bằng redirect_url trong config: https://app.example.com/oauth2/callback. “Chính xác” nghĩa là scheme, host, port và path phải khớp từng ký tự. Dấu gạch chéo ở cuối sẽ khiến URI trở thành một URI khác.

Đây là lỗi thường gặp nhất trong toàn bộ quá trình thiết lập. Lỗi xảy ra trước khi oauth2-proxy được gọi. Provider từ chối authorization request và hiển thị trang lỗi của chính nó, nên không có gì xuất hiện trong log của oauth2-proxy. Dấu hiệu nhận biết nằm trên thanh địa chỉ: trình duyệt vẫn đang ở domain của provider, còn query string chứa error=invalid_request hoặc trang hiển thị trực tiếp tên redirect_uri. Khi thấy dấu hiệu này, hãy sửa record của ứng dụng trong provider, không sửa proxy config.

Hãy copy issuer URL từ provider thay vì tự nhập. oauth2-proxy nối thêm /.well-known/openid-configuration vào oidc_issuer_url rồi fetch discovery document đó khi khởi động. Trước tiên, hãy tự kiểm tra:

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

JSON chứa key authorization_endpoint nghĩa là issuer URL chính xác. Phản hồi 404 hoặc trang lỗi HTML nghĩa là URL sai; oauth2-proxy cũng sẽ fail khi khởi động với chính lỗi 404 đó. Nếu bạn chưa chọn provider, bài so sánh Keycloak, Authentik và Zitadel phân tích các điểm đánh đổi, còn hướng dẫn chạy Authentik làm SSO server riêng trình bày phần provider của đúng mô hình này.

Nginx: auth_request

Nginx thực hiện forward auth bằng auth_request. Directive này tạo một sub-request nội bộ rồi xử lý tiếp dựa trên status code của sub-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;
  }
}

Ba chi tiết trong cấu hình này đều cần thiết. proxy_pass_request_body off với Content-Length rỗng ngăn nginx sao chép body của mọi POST vào sub-request. Điều này quan trọng vì oauth2-proxy không đọc body. Với file upload, cấu hình mặc định sẽ gửi file hai lần.

Cặp auth_request_set $auth_cookieadd_header Set-Cookie chuyển session cookie mới được refresh về browser. Nếu bỏ qua phần này, cookie_refresh sẽ âm thầm không làm gì vì nginx loại bỏ Set-Cookie của sub-request. Browser sẽ giữ giá trị cũ cho đến khi session hết hạn.

error_page 401 = @oauth2_signin biến một lần kiểm tra thất bại thành chuyển hướng đến trang login. Nếu không có nó, visitor chưa được xác thực sẽ chỉ nhận trang 401 Authorization Required trống và không có cách tiếp tục.

Luôn kiểm tra trước khi reload:

sudo nginx -t && sudo systemctl reload nginx

Nếu các directive xung quanh còn mới với bạn, phân tích cấu trúc của cấu hình nginx reverse proxy trình bày lớp bên dưới cấu hình này.

Traefik: middleware forwardAuth

Traefik cần 2 middleware cho cùng một mục đích. Một middleware thực hiện kiểm tra. Middleware còn lại chuyển phản hồi 401 thành redirect trên trình duyệt.

# 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

Gắn cả 2 middleware vào router đứng trước app của bạn, đồng thời publish oauth2-proxy trên một router riêng tại oauth.example.com, vì trình duyệt phải truy cập /oauth2/sign_in/oauth2/callback mà không đi qua bước kiểm tra.

statusRewrites chuyển 401 thành 302 là phần nhiều người bỏ sót. Nếu không có nó, Traefik trả về redirect đăng nhập với status 401, trình duyệt không follow redirect đó, và người truy cập thấy một trang chỉ chứa từ Found.

trustForwardHeader: true chuyển tiếp host và URI ban đầu đến oauth2-proxy. oauth2-proxy cần các giá trị này để tạo giá trị rd, dùng để đưa người dùng trở lại trang họ yêu cầu. Đặt whitelist_domains để bao gồm cả host đó; nếu không, oauth2-proxy sẽ loại bỏ parameter rd vì xem đây là rủi ro open redirect, và sau khi đăng nhập mọi người sẽ được đưa đến /. Một máy chủ Traefik thường đứng trước nhiều app cùng lúc. Bài định tuyến nhiều app Docker Compose qua một instance Traefik trình bày layout router mà cấu hình này tích hợp vào.

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
  }
}

Thứ tự ở đây rất quan trọng. Block /oauth2/* phải đứng trước và không có forward_auth, vì visitor chưa đăng nhập cần truy cập được các path sign-in và callback. Nếu đặt bước kiểm tra trước các path này, trang login sẽ redirect về chính nó cho đến khi browser từ bỏ.

copy_headers là thành phần chuyển identity vào request gửi đến upstream, và chỉ tạo ra các giá trị khi oauth2-proxy chạy với set_xauthrequest = true. Việc chọn proxy nào là một vấn đề riêng; phần so sánh nginx, Caddy và Traefik sẽ phân tích vấn đề đó.

Vì sao đăng nhập lại quay về trang đăng nhập?

Bạn đăng nhập tại provider, provider chuyển bạn quay lại, rồi oauth2-proxy lại chuyển thẳng bạn về provider. Vòng lặp này có nghĩa là request callback đến nơi mà không có cookie do oauth2-proxy đặt trước đó. Log của nó sẽ ghi rõ trường hợp này:

No cookies were found in OAuth callback.

Hoặc khi một cookie khác được gửi đến nhưng cookie cần thiết thì không:

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

CSRF là cross-site request forgery. Cookie này tồn tại để liên kết callback với lần đăng nhập đã bắt đầu. Trình duyệt sẽ hiển thị cùng một lỗi như sau:

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

Hãy kiểm tra 4 nguyên nhân sau theo thứ tự.

  1. cookie_secure = true trong khi trình duyệt truy cập site qua HTTP thuần. Trình duyệt sẽ không lưu cookie có thuộc tính Secure trên origin http://, nên cookie không bao giờ được gửi lại. Hãy cấu hình TLS (transport layer security) termination đúng cách, hoặc chỉ đặt cookie_secure = false khi kiểm thử trên localhost.
  2. Giá trị cookie_domains không bao phủ hostname trên thanh địa chỉ. .example.com bao phủ app.example.com và hoàn toàn không có tác dụng với app.example.net.
  3. Trình duyệt loại bỏ cookie. Extension bảo mật nghiêm ngặt hoặc tính năng chặn third-party cookie có thể xóa _oauth2_proxy_csrf trong khoảng thời gian giữa redirect đi và callback về.
  4. Clock bị lệch. Nếu clock của server lệch nhiều so với provider, iatexp trong ID token sẽ nằm ngoài khoảng thời gian được chấp nhận, khiến session bị từ chối ngay khi nhận. timedatectl phải báo cáo System clock synchronized: yes.

Hãy theo dõi sự cố từ phía server thay vì đoán:

sudo journalctl -u oauth2-proxy -f

Mở app trong private window. Mọi request đều được ghi kèm status, vì vậy callback ngay lập tức theo sau bởi một redirect khác đến provider chính là vòng lặp được ghi lại trong log.

Các path cần bỏ qua bước đăng nhập: API, webhook và websocket

Forward auth giả định trình duyệt đang giữ cookie. Client không có trình duyệt sẽ bị lỗi.

API client gửi Authorization: Bearer <token> không có cookie, nên nhận phản hồi 302 chuyển đến trang đăng nhập của provider, rồi cố phân tích HTML thành JSON. Có 2 cách xử lý rõ ràng. Đặt skip_jwt_bearer_tokens = true để oauth2-proxy chấp nhận bearer token JWT (JSON web token) hợp lệ từ cùng issuer. Cách này phù hợp khi API client của bạn đã nhận token từ provider. Nếu không, hãy loại trừ path đó:

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

Mỗi giá trị là một regular expression được so khớp với path đã chuẩn hóa, có thể thêm HTTP method và = ở đầu. POST=^/webhook/ cho phép webhook receiver nhận POST, trong khi người dùng truy cập cùng path bằng trình duyệt vẫn phải qua bước đăng nhập. Mỗi entry là một lỗ hổng trong lớp bảo vệ, vì vậy hãy neo expression bằng ^ và giới hạn phạm vi ở mức hẹp nhất mà caller cho phép.

Websocket là trường hợp nhiều người xử lý sai. Request upgrade là một HTTP GET thông thường, mang cùng cookie như mọi request khác, nên thường vượt qua bước kiểm tra và không cần được loại trừ. Vấn đề nằm ở phần proxying xung quanh request đó. Nếu location được bảo vệ không có các header UpgradeConnection, quá trình upgrade sẽ không hoàn tất và client của app sẽ retry vô hạn, kèm thông báo WebSocket connection ... failed trong browser console. Loại trừ path không giải quyết được vấn đề này, vì request đã được authorize từ trước.

Có một giới hạn thực tế cần lưu ý. Bước kiểm tra chỉ chạy 1 lần, tại thời điểm upgrade. Websocket duy trì trong nhiều giờ sẽ không được kiểm tra lại, nên việc xóa user khỏi provider không đóng socket mà user đó đang giữ. Hãy restart app để ngắt các connection đang hoạt động.

Mặc định, toàn bộ session nằm trong cookie và được mã hóa bằng cookie_secret của bạn. Cách này giúp oauth2-proxy không cần lưu state và không cần thêm service. Tuy nhiên, cookie có giới hạn vì trình duyệt thường giới hạn kích thước cookie ở khoảng 4 KB. Khi ID token chứa danh sách dài các group claim, oauth2-proxy sẽ chia session thành _oauth2_proxy_0, _oauth2_proxy_1 và các phần tiếp theo. Khi có quá nhiều phần, request header sẽ lớn đến mức nginx trả về 400 Request Header Or Cookie Too Large trước khi ứng dụng nhận được request.

Khi gặp trường hợp này, hãy chuyển session sang lưu phía server:

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

Khi đó, trình duyệt chỉ giữ một ticket ngắn, còn session đã mã hóa nằm trong Redis. Đổi lại, Redis phải luôn hoạt động. Nếu Redis ngừng hoạt động, mọi session sẽ mất hiệu lực và tất cả người dùng sẽ bị đăng xuất cùng lúc. Cookie store cũng có nhược điểm riêng: hai request cùng refresh một session tại cùng thời điểm có thể xung đột và buộc người dùng đăng nhập lại.

Forward auth không cung cấp những gì

Đây là một cổng kiểm soát ở phía trước. Nó không phải là cơ chế phân quyền bên trong ứng dụng. Sự khác biệt này quyết định cách tiếp cận này có phù hợp với trường hợp của bạn hay không.

Sau khi người dùng đi qua cổng, ứng dụng vẫn thấy đúng những gì nó luôn thấy. Nếu app có roles riêng, forward auth không tự điền các roles đó, trừ khi app hỗ trợ authentication dựa trên header và ánh xạ một header vào một account. Grafana hỗ trợ cách này thông qua các setting auth.proxy. Phần lớn app tự host không hỗ trợ, nên với ứng dụng, mọi người đi qua cổng đều là cùng một identity duy nhất. Identity đó thường là admin.

Forward auth cũng không bảo vệ các API token riêng của ứng dụng. Personal access token do app cấp dùng để xác thực với app, không phải với oauth2-proxy. Vì vậy token sẽ ngừng hoạt động ngay khi cổng được đặt phía trước nó. Nếu bỏ qua API path để token hoạt động lại, token đó sẽ trở thành lớp bảo vệ duy nhất của path. Lúc này bạn đang chạy 2 hệ thống authentication trên cùng một service, nhưng SSO chỉ áp dụng cho một hệ thống.

Revocation là khoảng trống thứ ba. Xóa user trong provider sẽ chặn các lần login mới và chặn việc refresh token do cookie_refresh thực hiện, nhưng session cookie hiện có vẫn hợp lệ cho đến khi hết hạn. cookie_expire mặc định là 168 hours, nghĩa là người vừa bị xóa vẫn có thể truy cập trong một tuần. Đặt cookie_refresh thành một khoảng thời gian ngắn, chẳng hạn một giờ, để revocation có hiệu lực trong khoảng thời gian đó.

Audit trail cũng chỉ dừng ở cổng. oauth2-proxy ghi log ai đã đi qua và vào thời điểm nào. Ứng dụng chỉ ghi log một session không có tên. Nếu bạn phải trả lời ai đã thay đổi một setting, header identity trong app là mức tối thiểu. Câu trả lời đáng tin cậy là dùng account riêng cho từng user.

Khi trả phí cho SSO là lựa chọn phù hợp hơn

Forward auth là công cụ phù hợp khi ứng dụng hoàn toàn không có đăng nhập hoặc chỉ dùng một mật khẩu chung, và bạn muốn có một nơi duy nhất để thêm hoặc xóa người dùng. Cách này tốn một buổi chiều và thêm một process, đồng thời hoạt động với mọi ứng dụng dùng HTTP.

Đây là công cụ không phù hợp khi mỗi người cần quyền khác nhau trong cùng một ứng dụng. Một cổng chặn không thể thể hiện rằng “Ana được phép chỉnh sửa dashboard còn Bo chỉ được đọc”. Nếu vendor bán gói SSO, tính năng ánh xạ group sang role thường là thứ bạn thực sự đang mua. Tự xây dựng tính năng này bằng header và proxy rule sẽ dễ lỗi hơn so với trả phí cho nó. Mô hình định giá đằng sau các gói SSO đó đáng để đọc trước khi bạn quyết định.

Hai tình huống khác cũng dẫn đến cùng kết luận. Công việc compliance yêu cầu audit record theo từng người dùng bên trong ứng dụng sẽ không chấp nhận access log của proxy làm bằng chứng. Ngoài ra, mọi ứng dụng có mobile client hoặc desktop client không gửi browser cookie sẽ xung đột với cổng chặn trong từng request.

FAQ

Forward auth là gì?

Forward auth là một mô hình trong đó reverse proxy hỏi một authentication service riêng về từng request đến trước khi chuyển request đó đến upstream. Proxy gửi các request header đến một endpoint như /oauth2/auth rồi đọc status code. Mã 202 nghĩa là request được cho phép, nên request ban đầu được chuyển tiếp đến application. Mã 401 nghĩa là không có session, nên proxy redirect browser đến trang đăng nhập. Nginx triển khai cơ chế này bằng directive auth_request, Traefik bằng middleware forwardAuth và Caddy bằng forward_auth.

Vì sao oauth2-proxy redirect tôi về trang đăng nhập theo vòng lặp?

Request callback đến oauth2-proxy nhưng không có CSRF cookie, nên oauth2-proxy khởi động lại flow. Server log ghi No cookies were found in OAuth callback. và browser hiển thị Login Failed: Unable to find a valid CSRF token. Please try again.. Nguyên nhân thường gặp là cookie_secure = true trên một site mà browser truy cập qua HTTP thuần, vì browser sẽ không lưu cookie Secure trên một origin http://. Nguyên nhân phổ biến tiếp theo là giá trị cookie_domains không bao phủ hostname trong thanh địa chỉ.

Làm thế nào để cho API client hoặc webhook đi qua oauth2-proxy?

Dùng skip_auth_routes với regular expression có anchor, và có thể giới hạn cho một HTTP method, ví dụ POST=^/webhook/. Nếu API client của bạn đã có JWT do cùng provider phát hành, skip_jwt_bearer_tokens = true sẽ chấp nhận các token đó thay cho cookie và vẫn bảo vệ path. Mọi thứ được liệt kê trong skip_auth_routes đều không cần authentication đối với tất cả mọi người, vì vậy hãy giữ mỗi expression hẹp nhất có thể theo phạm vi caller cần.

oauth2-proxy có cung cấp quyền theo từng user cho application không?

Không. Nó là một cổng kiểm soát, không phải hệ thống authorisation. Nó quyết định ai được truy cập application. Mọi người đã truy cập được đều trông giống nhau đối với application, trừ khi application đọc identity header và ánh xạ chúng với các account. Grafana hỗ trợ việc này thông qua các thiết lập auth.proxy. Phần lớn self-hosted app không hỗ trợ, nên mọi người vượt qua cổng kiểm soát sẽ dùng chung identity duy nhất mà app đang chạy với.

Có thể bypass oauth2-proxy bằng cách tự đặt identity header không?

Có, nếu họ có thể truy cập trực tiếp vào application. Identity được truyền qua một header thuần như X-Auth-Request-Email, và application tin vào mọi giá trị nhận được. Bất kỳ ai có thể mở connection đến application port đều có thể gửi header đó và giả làm bất kỳ user nào. Hãy bind app vào 127.0.0.1, hoặc giữ app trên một Docker network nội bộ không có published port, rồi xác nhận bằng sudo ss -tlnp.

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