Stop Implementing Authentication Inside Containers on Kubernetes

You have ten microservices running in Kubernetes. Each one validates JWTs, checks scopes, maintains sessions, and implements its own RBAC rules. One team uses jsonwebtoken v8, another uses a custom Go library, a third rolled their own HMAC check because “it was simple.” They all accept alg: none. Three accept RS256 and HS256 simultaneously.

This is not a security posture. This is a distributed security liability — and Kubernetes makes the problem worse, because the cluster creates an illusion of isolation that encourages teams to treat each Pod as a security boundary it was never designed to be.

The pattern of embedding authentication and authorization logic inside individual containers is one of the most pervasive anti-patterns in Kubernetes-based microservices. It feels like ownership and simplicity. It is, in practice, inconsistency at scale — and the blast radius of a single misconfiguration is your entire service portfolio.

Kubernetes provides the primitives to fix this at the infrastructure layer: Ingress controllers, the Gateway API, service mesh sidecars, admission webhooks, and workload identity via SPIFFE. None of these require a line of auth code inside your application containers.

This article explains why the anti-pattern exists, what’s wrong with it technically, and what the correct Kubernetes-native alternatives are — with concrete implementation guidance and references to the standards and incidents that validate the argument.


The Anti-Pattern: What It Looks Like

In-Application JWT Validation

Every service imports an auth library and validates tokens independently:

# Pattern seen in thousands of microservices
from jose import jwt

def authenticate(token: str):
    payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    return payload["sub"]

Variations include:

  • Algorithm confusion: accepting both HS256 and RS256, or letting the token header drive verification behavior instead of pinning acceptable algorithms server-side. This is a distinct JWT implementation failure class, documented extensively by PortSwigger on JWT attacks and RFC 8725
  • alg: none bypass: libraries that accept unsigned tokens when alg is set to none. This is a documented attack vector in Auth0’s JWT security analysis
  • Missing exp / iss / aud validation: trusting a valid signature without checking whether the token is expired, for the right audience, or from the right issuer
  • Key confusion attacks: accepting a public RS256 key as an HS256 symmetric secret

Session State in Every Service

When services maintain session state directly, they duplicate logic that has no business being duplicated — cookie validation, refresh token flows, PKCE verification — and each implementation diverges over time.

RBAC Reimplemented Per Service

Authorization rules (“can this user access this resource?”) end up embedded in service logic, mixed with business logic, tested inconsistently, and impossible to audit across the portfolio.


Why This Is a Structural Problem

1. The Vulnerability Surface Scales With Your Service Count

Each new microservice is a new JWT validation surface. A single incorrect library configuration — an unvalidated alg, a missing aud check — is an authentication bypass affecting that entire service. With ten services, you have ten potential misconfigurations. With a hundred services, the probability that at least one is misconfigured approaches certainty.

The OWASP Kubernetes Security Cheat Sheet and OWASP Microservices Security Cheat Sheet both identify in-service auth as a primary attack surface in microservices environments. NIST SP 800-204 and its companion NIST SP 800-204A on DevSecOps make the same argument: security controls belong at infrastructure boundaries, not inside application code.

2. Maintenance Cost Is Multiplicative

When a JWT vulnerability is disclosed — and they are disclosed regularly — you update one library in one service. Then another. Then you discover service C is pinned to an old version because it has a transitive dependency conflict. Meanwhile the vulnerability is exploitable in production.

The CNCF Cloud Native Security Whitepaper frames this directly: security controls implemented redundantly across services create maintenance overhead that teams cannot sustain, leading to version drift and policy divergence.

3. Centralized Policy Is Impossible to Enforce

When policy is in code — even well-factored library code — it cannot be changed atomically across services. A policy update requires a coordinated deployment across every affected service. In practice, services deploy on different schedules, managed by different teams, with different testing cycles. The result is that at any given moment, some fraction of your services are running different authorization rules.

This is the core argument in Google’s BeyondCorp model and the Zero Trust Architecture guidance from NIST SP 800-207: authentication and authorization decisions should be made by a centralized, auditable policy enforcement point — not distributed across workloads.

4. Secrets Distribution Is a Problem You Don’t Need

If every service validates JWTs, every service needs the signing key (for symmetric algorithms) or the public key (for asymmetric). Distributing and rotating signing keys across a fleet of microservices is an operational burden with meaningful blast radius: a leaked symmetric key compromises every service holding it.

The CNCF SPIFFE/SPIRE project was built specifically to solve this class of problem: workload identity should be cryptographically attested, not rely on secrets distributed to application code.


The Real-World Incidents

The alg: none Class

In 2015, critical vulnerabilities in JWT libraries from Auth0 affecting Python, PHP, Node.js, Ruby, Java, and .NET allowed attackers to forge tokens by setting alg: none. The signature was not verified. The vulnerability was present in applications that had copied JWT validation code from tutorials or used unpatched libraries — exactly the pattern that in-service auth produces at scale.

Java’s Psychic Signatures (CVE-2022-21449)

CVE-2022-21449 affected ECDSA signature verification in Oracle Java SE and GraalVM, including java.security.Signature paths used by higher-level libraries. The bug allowed certain malformed ECDSA signatures to verify when they should not. JWT validation was in scope only when the deployment used an affected Java runtime and ECDSA-signed tokens, for example ES256. A gateway would help only if verification happened on a patched or unaffected runtime at the gateway instead of inside every Java service.

CVE-2023-2728 (Kubernetes Mountable Secrets Bypass)

CVE-2023-2728 was not an ImagePolicyWebhook outage behavior. It was a Kubernetes API server issue where users could use ephemeral containers to bypass the mountable secrets policy enforced by the ServiceAccount admission plugin. Clusters were affected only when the ServiceAccount admission plugin, the kubernetes.io/enforce-mountable-secrets annotation, and ephemeral containers were used together. The adjacent ImagePolicyWebhook issue was CVE-2023-2727, also involving ephemeral containers, but it is a separate CVE.

The Uber API Gateway Evolution

Uber’s engineering blog describes its API gateway as a centralized layer for routing, protocol conversion, rate limiting, load shedding, header propagation, security auditing, and user access blocking. That supports the architectural point here: high-volume platforms move cross-cutting controls into shared infrastructure. The public source does not prove that Uber migrated specifically from per-service authentication to gateway authentication, so that stronger claim should not be made.

Netflix Zuul

Netflix’s Zuul is an L7 gateway for dynamic routing, monitoring, resiliency, security, and related edge concerns. Netflix’s own Zuul posts list authentication among common edge-service uses, but they do not frame Zuul primarily as a case study in eliminating per-service auth. Treat it as evidence that authentication is a natural edge concern at scale, not as proof of a specific migration story.


The Alternatives

Use these as complementary controls, not as a single replacement for all authentication and authorization logic:

AlternativeWhen to use itWhat it solvesPrincipal trade-off
API Gateway / Edge AuthExternal API clients, public ingress, partner integrations, mixed auth mechanisms at the boundaryCentral JWT/API-key/OAuth2 validation, rate limiting, request shaping, and identity header propagation before traffic reaches servicesDoes not secure east-west service calls by itself; trusted headers require strict network boundaries
Service Mesh mTLSService-to-service traffic inside the cluster, especially across teams or sensitive domainsWorkload identity, automatic mTLS, peer authentication, and proxy-level authorization policyAdds data-plane/control-plane complexity and operational coupling to sidecars or ambient mesh components
OAuth2 ProxyBrowser-facing internal apps that need OIDC login, redirects, cookies, and session handlingDelegates login/session management to a reverse proxy and forwards authenticated identity headersBest for HTTP/browser flows; not a general machine-to-machine authorization system
OPAComplex, auditable, frequently changing authorization rulesSeparates policy decisions from application releases and can run as sidecar, service, ext_authz backend, or admission control via GatekeeperPolicy/data distribution and failure behavior must be designed deliberately
SPIFFE/SPIREMulti-cluster, multi-cloud, or meshless environments that need portable workload identityIssues short-lived workload identities without application-managed shared secretsProvides identity, not business authorization; needs registration and attestation lifecycle management

Option 1: API Gateway (Edge Auth)

An API Gateway sits at the perimeter and handles authentication before a request reaches any downstream service. Services receive pre-validated identity in a trusted header.

What it does: – Validates JWTs, API keys, OAuth2 tokens – Enforces rate limiting per identity – Strips and re-adds Authorization headers as needed – Routes to upstream services with verified identity headers

When to use it: – North-south traffic (external clients → cluster) – Mixed authentication mechanisms (JWT + API key + mTLS) at the Ingress layer – Teams that want to centralize auth policy without rolling out a full service mesh

Tools:

Gravitee.io API Gateway can be deployed on Kubernetes via its Helm chart and integrates with the Kubernetes Gateway API:

helm repo add graviteeio https://helm.gravitee.io
helm install gravitee-apim graviteeio/apim \
  --namespace gravitee \
  --create-namespace \
  --set gateway.replicaCount=2 \
  --set gateway.ingress.enabled=true \
  --set gateway.ingress.hosts[0]=api.example.com

Once deployed, authentication policies are declared on the ApiV4 CRD — no application code involved:

apiVersion: gravitee.io/v1alpha1
kind: ApiV4
metadata:
  name: payment-api
  namespace: gravitee
spec:
  name: "Payment API"
  type: PROXY
  listeners:
    - type: HTTP
      paths:
        - path: /v1/payments
      entrypoints:
        - type: http-proxy
  endpointGroups:
    - name: default
      type: http-proxy
      endpoints:
        - name: upstream
          type: http-proxy
          configuration:
            target: http://payment-service.production.svc.cluster.local:8080
  flows:
    - name: JWT validation
      enabled: true
      request:
        - policy: jwt
          enabled: true
          configuration:
            signature: RSA_RS256
            publicKeyResolver: JWKS_URL
            jwksUrl: https://idp.example.com/.well-known/jwks.json
            checkTokenRevocation: true
            requiredClaims:
              - name: aud
                value: payment-api
        - policy: rate-limit
          enabled: true
          configuration:
            rate:
              limit: 100
              periodTime: 1
              periodTimeUnit: MINUTES

Gravitee’s Kubernetes operator reconciles ApiV4 resources against the gateway, making API policy a first-class GitOps object — versioned, reviewed, and deployed the same way as any other Kubernetes manifest.

Other Kubernetes-native options: Emissary-Ingress (formerly Ambassador) with its AuthService CRD; Traefik with ForwardAuth middleware on IngressRoute resources.

Limitations: API Gateways handle north-south traffic. They don’t address east-west (service-to-service) authentication inside the cluster.


Option 2: Service Mesh (East-West mTLS + Auth)

A service mesh provides mutual TLS between every service pair and enforces authorization policy at the sidecar proxy, without any application code changes.

What it does: – Automatic mTLS between all service-to-service calls – Workload identity via X.509 certificates (SPIFFE SVIDs) – Fine-grained AuthorizationPolicy at the Envoy sidecar – JWT validation at the proxy, not the application

Istio Implementation:

Istio uses Envoy’s ext_authz filter and native RequestAuthentication + AuthorizationPolicy CRDs:

# RequestAuthentication — validate JWTs at the proxy
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: require-jwt
  namespace: production
spec:
  selector:
    matchLabels:
      app: payment-service
  jwtRules:
    - issuer: "https://accounts.google.com"
      jwksUri: "https://www.googleapis.com/oauth2/v3/certs"
      audiences:
        - "my-api-audience"
      forwardOriginalToken: false
---
# AuthorizationPolicy — enforce after JWT validation
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: payment-service-authz
  namespace: production
spec:
  selector:
    matchLabels:
      app: payment-service
  action: ALLOW
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/production/sa/order-service"]
      to:
        - operation:
            methods: ["POST"]
            paths: ["/v1/payments"]
      when:
        - key: request.auth.claims[scope]
          values: ["payments:write"]

The RequestAuthentication policy tells Envoy how to validate JWTs. The AuthorizationPolicy specifies what authenticated principals are allowed to do. Neither policy lives in application code.

The payment service receives the validated request — or a 401/403 from the proxy, before the request touches application code.

Linkerd:

Linkerd provides automatic mTLS with SPIFFE-compliant workload identity. Its policy model is simpler than Istio but sufficient for most service-to-service auth requirements:

apiVersion: policy.linkerd.io/v1beta3
kind: Server
metadata:
  name: payment-server
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: payment-service
  port: 8080
---
apiVersion: policy.linkerd.io/v1beta3
kind: ServerAuthorization
metadata:
  name: order-to-payment
  namespace: production
spec:
  server:
    name: payment-server
  client:
    meshTLS:
      serviceAccounts:
        - name: order-service

This is mutual TLS + SPIFFE-based identity, enforced at the proxy. The application doesn’t implement it; the mesh does.

Istio + Envoy External Authorization:

For more complex policy (e.g., OPA integration), Envoy’s ext_authz filter delegates authorization to an external service:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: ext-authz-filter
  namespace: production
spec:
  workloadSelector:
    labels:
      app: payment-service
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_INBOUND
        listener:
          filterChain:
            filter:
              name: "envoy.filters.network.http_connection_manager"
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.ext_authz
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
            grpc_service:
              envoy_grpc:
                cluster_name: outbound|9191||opa.production.svc.cluster.local
            timeout: 0.5s
            failure_mode_allow: false

The CNCF TAG Security paper on microservices security documents this architecture as the reference pattern for production Kubernetes environments.


Option 3: OAuth2 Proxy (Delegated Auth for HTTP)

OAuth2 Proxy is a reverse proxy that authenticates requests against an OAuth2/OIDC provider and passes validated identity downstream. With 14,000+ GitHub stars and active maintenance, it is the most widely deployed solution for this pattern in Kubernetes.

What it does: – Sits in front of one or more upstream services – Redirects unauthenticated requests to an OIDC provider (Keycloak, Dex, Google, GitHub, etc.) – Validates tokens, manages sessions, handles refresh – Passes X-Auth-Request-User, X-Auth-Request-Email, X-Auth-Request-Groups headers downstream

Kubernetes deployment with Nginx Ingress:

# OAuth2 Proxy deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
  namespace: auth
spec:
  replicas: 2
  selector:
    matchLabels:
      app: oauth2-proxy
  template:
    metadata:
      labels:
        app: oauth2-proxy
    spec:
      containers:
        - name: oauth2-proxy
          image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
          args:
            - --provider=oidc
            - --oidc-issuer-url=https://keycloak.example.com/realms/myrealm
            - --client-id=$(CLIENT_ID)
            - --client-secret=$(CLIENT_SECRET)
            - --cookie-secret=$(COOKIE_SECRET)
            - --http-address=0.0.0.0:4180
            - --reverse-proxy=true
            - --upstream=static://202
            - --email-domain=*
            - --set-xauthrequest=true
            - --cookie-secure=true
            - --skip-provider-button=true
          env:
            - name: CLIENT_ID
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: client-id
            - name: CLIENT_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: client-secret
            - name: COOKIE_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: cookie-secret
---
# Ingress annotation to protect a service with OAuth2 Proxy
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: protected-service
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "https://oauth2-proxy.example.com/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "https://oauth2-proxy.example.com/oauth2/start?rd=$escaped_request_uri"
    nginx.ingress.kubernetes.io/auth-response-headers: "X-Auth-Request-User,X-Auth-Request-Email,X-Auth-Request-Groups"
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: protected-service
                port:
                  number: 8080

The upstream service receives X-Auth-Request-User and X-Auth-Request-Groups as trusted headers — it never sees a token, never validates a signature, never imports a JWT library.

When to use OAuth2 Proxy vs a service mesh: OAuth2 Proxy handles north-south browser-facing traffic with session management (login flows, redirects, cookies). A service mesh handles east-west machine-to-machine auth. They are complementary, not alternatives.


Option 4: Open Policy Agent (OPA / OPAL)

OPA decouples policy from code entirely. Authorization logic is written in Rego and evaluated by OPA as a sidecar or as a centralized service. Applications query OPA for allow/deny decisions.

# Rego policy — payment service authorization
package payments.authz

import future.keywords.if
import future.keywords.in

default allow := false

allow if {
    input.method == "POST"
    input.path == "/v1/payments"
    "payments:write" in input.token.scope
    input.token.iss == "https://accounts.example.com"
}

allow if {
    input.method == "GET"
    startswith(input.path, "/v1/payments/")
    "payments:read" in input.token.scope
}

Application code becomes:

// The ONLY auth code in the application
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        input := map[string]interface{}{
            "method": r.Method,
            "path":   r.URL.Path,
            "token":  extractToken(r),
        }
        
        result, err := opaClient.Decision(r.Context(), "payments/authz", input)
        if err != nil || !result.Allow {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return
        }
        next.ServeHTTP(w, r)
    })
}

OPAL (Open Policy Administration Layer) adds real-time policy and data updates to OPA deployments — policy changes propagate to all OPA instances within seconds without redeployment.

Kubernetes deployment patterns for OPA:

As a sidecar — OPA runs in the same Pod as the application, evaluating policy over a local socket. Zero network hop, no external dependency:

# Pod template fragment
spec:
  containers:
    - name: payment-service
      image: payment-service:latest
    - name: opa
      image: openpolicyagent/opa:0.63.0
      args:
        - run
        - --server
        - --addr=localhost:8181
        - /policy
      volumeMounts:
        - name: opa-policy
          mountPath: /policy
          readOnly: true
  volumes:
    - name: opa-policy
      configMap:
        name: payment-policy

As a centralized service with Envoy ext_authz — OPA exposes a gRPC endpoint that Istio’s Envoy sidecar calls for every request. Policy is enforced at the proxy, before the application receives the request. This is the pattern used alongside Istio’s EnvoyFilter shown in the service mesh section above.

As OPA GatekeeperOPA Gatekeeper runs as a Kubernetes admission webhook and enforces policies at deploy time, not at runtime. It’s the right tool for preventing misconfigured workloads from being deployed — for example, rejecting any Pod spec that sets hostNetwork: true or defines auth-related environment variables directly. This is complementary to runtime auth enforcement.

OPA is used at scale by Atlassian, Goldman Sachs, Netflix, Chef, and many others, documented in OPA’s production deployments. The CNCF OPA project graduated in 2021.


Option 5: SPIFFE/SPIRE (Workload Identity)

SPIFFE (Secure Production Identity Framework for Everyone) and SPIRE solve the problem of how workloads prove their identity without distributing secrets.

SPIRE issues short-lived X.509 SVIDs (SPIFFE Verifiable Identity Documents) to workloads. Each SVID encodes a SPIFFE URI:

spiffe://example.org/ns/production/sa/payment-service

Services authenticate each other using mTLS with these certificates. No JWT library. No shared secret. No secret distribution problem. Certificate rotation happens automatically every few hours.

# SPIRE Agent DaemonSet on Kubernetes
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: spire-agent
  namespace: spire
  labels:
    app: spire-agent
spec:
  selector:
    matchLabels:
      app: spire-agent
  template:
    metadata:
      labels:
        app: spire-agent
    spec:
      serviceAccountName: spire-agent
      hostPID: true
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
        - name: spire-agent
          image: ghcr.io/spiffe/spire-agent:1.9.0
          args: ["-config", "/run/spire/config/agent.conf"]
          volumeMounts:
            - name: spire-config
              mountPath: /run/spire/config
              readOnly: true
            - name: spire-agent-socket
              mountPath: /run/spire/sockets
              readOnly: false
      volumes:
        - name: spire-config
          configMap:
            name: spire-agent
        - name: spire-agent-socket
          hostPath:
            path: /run/spire/sockets
            type: DirectoryOrCreate

SPIFFE is the foundation of Istio’s workload identity model. Linkerd implements SPIFFE-compatible SVIDs. If you’re using a service mesh, you already have SPIFFE — the mesh uses it transparently.

Standalone SPIRE is appropriate for environments without a service mesh, or for multi-cluster/multi-cloud scenarios where a consistent workload identity layer is needed across boundaries.

SPIFFE/SPIRE graduated from the CNCF sandbox to incubating in 2019 and is deployed at Uber, Bloomberg, ByteDance, and Anthem.


Combining the Layers

These tools are not mutually exclusive — they address different traffic patterns and different problems:

LayerToolAddresses
Edge (north-south)API Gateway (Gravitee, Emissary, AWS APIGW)External clients → cluster
Browser sessionsOAuth2 ProxyBrowser-facing apps with login flows
East-west mTLSService Mesh (Istio/Linkerd) + SPIFFEService-to-service identity
PolicyOPA/OPALFine-grained, auditable authorization
Workload identitySPIREMulti-cloud/multi-cluster identity

A production deployment at reasonable scale looks like:

  1. External traffic hits an API Gateway or Ingress controller with OAuth2 Proxy
  2. The gateway validates the token, strips it, and forwards identity headers to the upstream service
  3. Inside the cluster, all service-to-service calls are mTLS via a service mesh, using SPIFFE workload identity
  4. Authorization decisions (beyond identity) are delegated to OPA
  5. Application code contains zero JWT validation, zero session management, zero auth library imports

Migration Path

If you already have auth embedded in services, migration doesn’t require a big bang rewrite.

Phase 1: Introduce the Gateway

Deploy an API Gateway or OAuth2 Proxy at the edge. Initially, services continue to validate tokens themselves as a backup — the gateway validates first. Use this phase to verify the gateway’s behavior and build confidence.

Phase 2: Trust the Gateway

Add a service-level feature flag: if a trusted X-Auth-Request-User header is present (set by the gateway), skip internal JWT validation. This decouples service auth from gateway rollout.

Phase 3: Remove In-Service Auth

Once all entry points are covered by the gateway and you have confidence in its reliability, remove the auth code from services. This is the step that actually reduces your attack surface.

Phase 4: Add East-West (Optional)

If east-west service-to-service calls exist and carry sensitive data, introduce a service mesh for mTLS. This is a separate effort from gateway auth and can proceed independently.


Decision Framework

External traffic entering the cluster (Ingress / Gateway API)?
├── Browser-facing app with login flow → OAuth2 Proxy (Nginx Ingress annotations)
└── API clients with tokens → API Gateway (Gravitee ApiV4 / Emissary AuthService)

Service-to-service calls inside the cluster (east-west)?
├── mTLS + identity sufficient → Service Mesh (Istio/Linkerd)
├── Identity across multiple clusters → SPIFFE/SPIRE standalone
└── Istio + fine-grained policy → RequestAuthentication + OPA via ext_authz

Complex, auditable authorization logic?
└── OPA as sidecar or ext_authz (runtime) + OPA Gatekeeper (admission)

Preventing misconfigured workloads from being deployed?
└── OPA Gatekeeper admission webhook

What to Keep in Application Code

Not everything should be removed. The correct model is:

  • Remove: JWT signature verification, token parsing, OAuth2 flows, session management
  • Keep: Business-level authorization (“can this user edit this specific resource?”), assuming identity is provided by infrastructure
  • Keep: Authorization errors surfaced correctly (403 vs 401, meaningful error bodies)
  • Keep: Structured logging of authorization decisions for audit trails

The application receives an authenticated identity from infrastructure. What the application does with that identity — which records to show, which operations to allow based on ownership — is correctly application logic.


Audit Checklist: Moving Auth Out of Containers

Use this as a practical exit checklist for the anti-pattern:

  • Inventory every service that imports JWT, OAuth2, OIDC, session, or custom RBAC libraries.
  • Classify each entry point as north-south, browser session, east-west service call, deploy-time admission policy, or business authorization.
  • Put one enforcing control in front of each class: API Gateway or OAuth2 Proxy for ingress, service mesh mTLS for east-west, OPA for shared policy, and SPIFFE/SPIRE for portable workload identity.
  • Pin JWT issuers, audiences, algorithms, and JWKS sources in infrastructure policy; do not let application code infer them from token headers.
  • Strip client-supplied identity headers at the edge and re-add trusted identity headers only after verification.
  • Define failure behavior explicitly: fail closed for authentication and authorization, and document any temporary fail-open exception with an owner and expiry date.
  • Remove in-service token verification only after every ingress path is covered, logs prove the infrastructure control is enforcing, and rollback has been tested.
  • Keep resource-level business authorization in application code, but feed it an identity established by infrastructure.

References

Standards and Frameworks – NIST SP 800-204: Security Strategies for MicroservicesNIST SP 800-204A: Building Secure Microservices-based Applications Using Service-Mesh ArchitectureNIST SP 800-207: Zero Trust ArchitectureOWASP Kubernetes Security Cheat SheetOWASP Microservices Security Cheat SheetCNCF Cloud Native Security Whitepaper v2RFC 7519: JSON Web Token (JWT)RFC 8725: JSON Web Token Best Current Practices

Vulnerabilities and Attack Classes – CVE-2022-21449: Java Psychic Signatures (ECDSA bypass)analysis by Neil MaddenCVE-2023-2728: Kubernetes mountable secrets policy bypassCVE-2023-2727: Kubernetes ImagePolicyWebhook bypassAuth0: Critical Vulnerabilities in JSON Web Token Libraries (alg:none, RS/HS confusion)PortSwigger Web Security Academy: JWT attacksjwt.io: Debugger and library reference

Tools and Projects – OAuth2 Proxy (GitHub — 14k+ stars)OAuth2 Proxy DocumentationGravitee.io API Gateway — JWT PolicyGravitee Kubernetes OperatorGravitee Helm ChartEmissary-Ingress AuthServiceOPA GatekeeperIstio Security: RequestAuthentication and AuthorizationPolicyEnvoy External Authorization FilterLinkerd Server PolicyOpen Policy AgentOPAL — Open Policy Administration LayerSPIFFE — Secure Production Identity Framework for EveryoneSPIRE — SPIFFE Runtime EnvironmentNetflix Zuul (GitHub)Traefik ForwardAuth Middleware

Architecture and Industry Context – Google BeyondCorp: A New Approach to Enterprise SecurityGoogle BeyondCorp Research Paper (USENIX ;login:)Netflix Tech Blog: Zuul 2 — The Netflix Journey to Asynchronous, Non-Blocking SystemsSPIFFE/SPIRE CNCF Graduation AnnouncementOPA CNCF GraduationInfoQ: Microservices Authentication and Authorization Anti-PatternsAWS re:Invent: Zero Trust Networking on AWSIstio Service Mesh Security ArchitectureThe CNCF TAG Security Microservices Security Paper


Article reflects tooling as of 2026: Kubernetes 1.29+, Istio 1.21+, Linkerd 2.15+, OPA 0.63+ / Gatekeeper 3.16+, SPIRE 1.9+, OAuth2 Proxy 7.6+, Gravitee APIM 4.x.

Sources