Hướng dẫn · 03/10/2026

NGINX auth_request thực chiến: External authorization an toàn

Khi nhiều ứng dụng dùng chung SSO, đưa logic xác thực vào từng service dễ tạo policy lệch nhau. Module auth_request cho phép NGINX gửi một subrequest nhỏ tới authorization service trước khi proxy request chính; chỉ request được chấp thuận mới tới upstream.

NGINX auth_request thực chiến: External authorization an toàn

Khi nhiều ứng dụng dùng chung SSO, đưa logic xác thực vào từng service dễ tạo policy lệch nhau. Module auth_request cho phép NGINX gửi một subrequest nhỏ tới authorization service trước khi proxy request chính; chỉ request được chấp thuận mới tới upstream.

Bài viết này xây cấu hình fail-closed, truyền identity header an toàn, xử lý 401/403, timeout, cache, CORS, WebSocket và observability. Nội dung được đối chiếu với tài liệu chính thức của ngx_http_auth_request_module, ngx_http_proxy_module, satisfy directive và ngx_http_log_module.

1. auth_request thực sự làm gì?

Với mỗi request được bảo vệ, NGINX tạo subrequest nội bộ tới URI auth:

  • auth trả 2xx: request chính được tiếp tục;
  • auth trả 401 hoặc 403: client nhận status tương ứng;
  • với 401, NGINX chuyển WWW-Authenticate từ subrequest;
  • mọi status khác, kể cả 3xx và 5xx, được coi là lỗi.

Response body của auth service không trở thành response body chính. Endpoint auth nên trả status và header nhỏ, không render HTML/JSON lớn. 204 No Content là kết quả allow rõ ràng.

2. Kiểm tra module trước khi triển khai

Module không được build mặc định trong mọi bản NGINX. Kiểm tra binary thực sự chạy:

nginx -V 2>&1 | grep -- --with-http_auth_request_module

Với package hoặc container image, pin digest/version và chạy smoke test trong CI. Đừng suy luận từ file cấu hình mẫu rằng binary production có module. NGINX Plus, distribution package và custom build có tập module khác nhau.

3. Cấu hình tối thiểu fail-closed

upstream auth_backend {
    server 127.0.0.1:9001;
    keepalive 32;
}

upstream app_backend {
    server 127.0.0.1:9000;
    keepalive 64;
}

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

    location /private/ {
        auth_request /_auth;
        proxy_pass http://app_backend;
    }

    location = /_auth {
        internal;
        proxy_pass http://auth_backend/verify;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_method GET;
        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header X-Original-Method $request_method;
        proxy_set_header X-Request-ID $request_id;
        proxy_set_header Authorization $http_authorization;
        proxy_set_header Cookie $http_cookie;
    }
}

internal ngăn client gọi trực tiếp /_auth. Tắt request body giúp tránh gửi lại upload lớn cho auth service. Ép subrequest thành GET làm contract ổn định; method gốc được chuyển trong header riêng để policy vẫn phân biệt read/write.

4. Auth service cần contract nhỏ và xác định

StatusÝ nghĩaHeader đề xuất
204Đã xác thực và được phépX-Auth-User, X-Auth-Roles
401Thiếu/sai credentialWWW-Authenticate
403Đã xác thực nhưng không có quyềnReason code nội bộ, không lộ policy chi tiết
5xxAuth service lỗiRequest ID để correlate

Không trả 302 tới trang login từ auth endpoint vì auth_request coi nó là lỗi. Với browser app, dùng route login rõ ràng ở frontend/app hoặc ánh xạ 401 có kiểm soát, đồng thời bảo vệ return URL khỏi open redirect.

5. Truyền identity bằng auth_request_set

location /private/ {
    auth_request /_auth;

    auth_request_set $auth_user  $upstream_http_x_auth_user;
    auth_request_set $auth_roles $upstream_http_x_auth_roles;
    auth_request_set $auth_state $upstream_status;
    auth_request_set $auth_time  $upstream_response_time;

    proxy_set_header X-Authenticated-User  $auth_user;
    proxy_set_header X-Authenticated-Roles $auth_roles;
    proxy_set_header Authorization "";

    proxy_pass http://app_backend;
}

auth_request_set chụp biến sau khi subrequest hoàn tất. Việc ghi đè X-Authenticated-* bằng giá trị do auth service trả ngăn client tự giả mạo header. Xóa Authorization trước app nếu app không cần token gốc; nếu cần, quyết định phải được review rõ.

Upstream application chỉ được nhận traffic từ NGINX qua private network, Unix socket hoặc mTLS. Nếu client có thể gọi thẳng app backend, nó có thể tự gửi identity header và bỏ qua gateway.

6. Chỉ chuyển dữ liệu auth service thật sự cần

Không proxy toàn bộ header một cách vô thức. Auth service thường cần:

  • Authorization hoặc đúng cookie session;
  • method và URI gốc;
  • host/tenant đã canonicalize;
  • request ID;
  • client IP chỉ khi trust chain đã cấu hình đúng.

$request_uri gồm query string, có thể chứa dữ liệu nhạy cảm. Nếu policy chỉ phụ thuộc path, chuyển $uri thay vì toàn bộ request URI. Không log cookie/token. Giới hạn tổng kích thước header và từ chối credential mơ hồ có nhiều nguồn cùng lúc.

7. Client IP và proxy trust chain

Không tin trực tiếp X-Forwarded-For do client gửi. Nếu NGINX đứng sau load balancer, cấu hình Real IP module với dải proxy tin cậy, rồi gửi địa chỉ đã chuẩn hóa:

set_real_ip_from 10.20.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

proxy_set_header X-Auth-Client-IP $remote_addr;

Sai trust chain biến IP allowlist/risk scoring thành bypass. Policy quyền cốt lõi không nên chỉ dựa vào IP; NAT và shared proxy khiến nhiều người có cùng địa chỉ.

8. Timeout, retry và hành vi khi auth service lỗi

location = /_auth {
    internal;
    proxy_pass http://auth_backend/verify;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_method GET;

    proxy_connect_timeout 500ms;
    proxy_send_timeout 1s;
    proxy_read_timeout 2s;
    proxy_next_upstream error timeout http_502 http_503 http_504;
    proxy_next_upstream_tries 2;
}

Auth outage mặc định phải fail closed. Đừng dùng stale “allow” vô thời hạn để giữ availability. Retry chỉ an toàn khi verify endpoint là idempotent và có tổng deadline nhỏ hơn SLA request. Dùng keepalive, nhiều instance và circuit/load shedding ở auth tier thay vì kéo timeout dài.

9. Cache quyết định auth rất dễ sai

NGINX có thể cache authorization subrequest, nhưng cache key phải phản ánh mọi input policy: credential/session, method, resource, tenant và đôi khi client context. Thiếu một chiều có thể trả quyền của user này cho user khác.

Nếu cache:

  • TTL allow ngắn hơn yêu cầu revocation;
  • không cache 401/403 quá lâu;
  • không đưa token plaintext vào log hoặc metric label;
  • không serve stale allow khi auth service lỗi trừ khi threat model chấp nhận;
  • test logout, role change và token revocation.

Cache trong auth service thường dễ thiết kế và audit hơn vì nó hiểu token expiry, policy version và revocation.

10. Location matching là nơi bypass thường xuất hiện

auth_request được áp theo context/inheritance. Một location regex hoặc location cụ thể có thể vô tình thay cấu hình:

location /private/ { auth_request /_auth; ... }

# Cần review: location này có thể xử lý file dưới /private/
location ~ \.php$ { ... }

Audit cấu hình sau include bằng nginx -T, không chỉ file đang sửa. Test mọi path variant: slash, encoded path, static extension, error page, internal redirect và named location. Nếu dùng satisfy any, một rule IP allow có thể đủ để bỏ qua auth; chỉ dùng khi đó chính là policy mong muốn.

11. CORS, WebSocket và streaming

  • CORS preflight: quyết định rõ OPTIONS có cần auth. Nếu bypass, location đó chỉ trả preflight và không được proxy dữ liệu ứng dụng.
  • WebSocket: auth_request bảo vệ HTTP upgrade ban đầu; quyền không được kiểm tra lại cho từng frame. Token expiry/revocation cần reconnect policy.
  • Streaming/SSE: auth chỉ xảy ra lúc mở connection. Connection dài có thể sống sau khi session bị revoke.
  • Upload: body không gửi tới auth, nhưng request chính vẫn có thể được buffer trước access phase tùy cấu hình. Test disk/temp-file impact.

Authorization tại gateway không thay thế object-level authorization bên trong ứng dụng. App vẫn phải kiểm tra user có quyền với record cụ thể.

12. Logging và metric tách auth khỏi app

log_format gateway
  '$request_id $remote_addr "$request" status=$status '
  'auth_status=$auth_state auth_time=$auth_time '
  'app_status=$upstream_status total=$request_time';

access_log /var/log/nginx/gateway-access.log gateway;

Vì $upstream_status sau đó có thể phản ánh main upstream, hãy capture status/time của auth bằng auth_request_set. Theo dõi:

  • tỷ lệ allow/401/403/auth-error;
  • p50/p95/p99 auth latency;
  • timeout, retry, connection failure;
  • auth error theo instance/deployment;
  • chênh lệch gateway 2xx và app authorization denial.

Không log Authorization, Cookie, identity claim nhạy cảm hoặc query string nguyên bản nếu không cần. Request ID phải xuyên suốt client → NGINX → auth → app.

13. Kiểm thử và rollout

nginx -t
nginx -T > /tmp/nginx-expanded.conf

curl -i https://app.example.com/private/profile
curl -i -H 'Authorization: Bearer invalid' \
  https://app.example.com/private/profile
curl -i -H 'X-Authenticated-User: admin' \
  https://app.example.com/private/profile

Test matrix phải gồm: không credential, token sai/hết hạn, allow, forbidden, auth timeout/5xx, header giả mạo, URI encoded, CORS OPTIONS, WebSocket upgrade và app backend bị gọi trực tiếp. Rollout canary, theo dõi auth latency/error rồi mới mở toàn traffic.

Checklist production

  • Binary có ngx_http_auth_request_module.
  • Auth location dùng exact match và internal.
  • Subrequest không gửi body, Content-Length rỗng và method contract rõ.
  • Identity header được overwrite từ auth response, không tin client.
  • App backend không thể truy cập công khai ngoài gateway.
  • Timeout ngắn, retry hữu hạn, lỗi auth fail closed.
  • Cache key/TTL/revocation đã threat-model hoặc không cache.
  • nginx -T được audit để tìm location bypass và satisfy any.
  • CORS/WebSocket/streaming có policy vòng đời rõ.
  • Log tách auth status/latency và redaction token/cookie.

Kết luận

auth_request biến NGINX thành điểm thực thi authorization gọn: một subrequest quyết định cho qua, từ chối hoặc fail closed trước khi traffic tới ứng dụng. Giá trị thực sự đến từ contract status nhỏ, identity header không giả mạo được, timeout ngắn, location coverage đầy đủ và observability tách riêng auth. Gateway bảo vệ biên; ứng dụng vẫn giữ trách nhiệm authorization ở mức tài nguyên.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.