External Authentication Policy in NGINX Ingress Controller: A Real World Use Case

by

in , ,

NGINX Ingress Controller 5.5.0 introduced ExternalAuth, a new Policy type that lets you define external authentication once in a Policy resource and apply it consistently across both VirtualServer and Ingress traffic paths. This is the second blog post in a two part series that covers the ExternalAuth Policy, and is focused on a real world use case. You can find the first blog post in this series here.

Centralizing Auth Without Touching Application Code

A platform engineer is running a dozen microservices on Kubernetes behind NGINX Ingress Controller. The company already has an OAuth2 provider for employee SSO — GitHub in this example. Today, each team handles auth differently: one service uses a Go middleware library, another team rolled their own JWT validation, and a couple of internal services have no auth at all behind an “it’s on the private network” assumption. A security review flags the inconsistency, and the platform engineer is asked to enforce authentication across the board, but the dev teams are mid-sprint and won’t add auth middleware to their services right now.

The platform engineer needs to:

  • Protect the employee dashboard (/app) with OAuth2 so employees sign in with their existing identity provider — no new passwords, no code changes in the dashboard service.
  • Protect the webhook receiver (/hooks) with HTTP Basic Auth for service-to-service calls from the CI pipeline — lightweight, no browser flow, just a shared credential validated against an htpasswd file.
  • Do this entirely at the ingress layer, without modifying or redeploying any application containers.

ExternalAuth policy solves this in two Policy resources and one VirtualServer update.

Step 1: Define the OAuth2 policy

Deploy oauth2-proxy configured with the company’s GitHub OAuth App, then create a Policy that points to it:

apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: github-oauth2
  namespace: platform
spec:
  externalAuth:
    authURI: "/oauth2/auth"
    authSigninURI: "/oauth2/signin"
    authServiceName: "oauth2-proxy-svc"
    sslEnabled: true
    sslVerify: true
    sslVerifyDepth: 2
    sniName: "external-auth-tls"
    trustedCertSecret: "external-auth-ca-secret"

Any route referencing this policy will redirect unauthenticated browser requests into the OAuth2 sign-in flow. Authenticated requests pass through to the backend with the identity headers set by oauth2-proxy.

Step 2: Define the Basic Auth policy

For the CI webhook path, the platform engineer deploys a small basic-auth service backed by an htpasswd secret and creates a second Policy:

apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: ci-basic-auth
  namespace: platform
spec:
  externalAuth:
    authURI: "/auth"
    authServiceName: "basic-auth-svc"
    sslEnabled: true
    sslVerify: true
    sslVerifyDepth: 2
    sniName: "external-auth-tls"
    trustedCertSecret: "external-auth-ca-secret"

Step 3: Attach the policies to routes

Both services share the same host, so they live in a single VirtualServer. Attach each policy at the route level so each path gets exactly the auth method it needs:

apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
  name: platform
  namespace: platform
spec:
  host: platform.yourcompany.com
  tls:
    secret: platform-tls-secret
    redirect:
      enable: true
  upstreams:
  - name: dashboard
    service: dashboard-svc
    port: 8080
  - name: webhooks
    service: webhook-receiver-svc
    port: 8080
  routes:
  - path: /app
    action:
      pass: dashboard
    policies:
      - name: github-oauth2
  - path: /hooks
    action:
      pass: webhooks
    policies:
      - name: ci-basic-auth

What This Achieves

Employees browsing to platform.yourcompany.com/app are redirected to the OAuth2 sign-in page if they don’t have a valid session. CI jobs hitting platform.yourcompany.com/hooks authenticate with a service credential validated against the htpasswd file. Neither path ever reaches an application container without passing through the auth service first, and none of this required a single line of code in the dashboard or webhook services.

When a new microservice joins the platform later, the platform engineer just adds a route and references the appropriate policy. No new auth libraries, no dev team coordination, no per-service configuration drift.

Production Readiness Checks

Before rollout, validate these explicitly:

  • Unauthenticated requests receive a 401 and never reach backend pods.
  • If authSigninURI is set, unauthenticated browser requests are redirected rather than rejected.
  • The OAuth2 callback path (e.g. /oauth2/callback) is reachable on the same host.
  • trustedCertSecret contains the correct CA and sniName matches the auth service certificate’s SAN.

Security Considerations

External auth operates at the Ingress edge, so keep it tight:

  • Use sslVerify: true and a trustedCertSecret in production; skipping verification exposes the auth subrequest to interception within the cluster network.
  • Use authSnippets sparingly. Its contents are inserted verbatim into the auth location block and must be treated with the same care as any other NGINX configuration change.
  • The auth service is in your critical path; if it becomes unavailable, NGINX returns 500 to clients.
  • Review ExternalAuth policy changes with the same care as other auth-related configuration changes.
  • Because NGINX location blocks are defined at the server level, only one sign-in redirect location can exist per host. If multiple routes on the same host reference ExternalAuth policies with authSigninURI, NIC uses the sign-in redirect configuration from the first policy it processes. All routes on the same host that require OAuth2 sign-in must use the same OAuth2 Proxy backend. If you need different OAuth2 providers for different routes, use separate hosts.

You can find complete working examples on GitHub and more documentation in our docs:

NGINX Community Forum