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
HS256andRS256, 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: nonebypass: libraries that accept unsigned tokens whenalgis set tonone. This is a documented attack vector in Auth0’s JWT security analysis- Missing
exp/iss/audvalidation: 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:
| Alternative | When to use it | What it solves | Principal trade-off |
|---|---|---|---|
| API Gateway / Edge Auth | External API clients, public ingress, partner integrations, mixed auth mechanisms at the boundary | Central JWT/API-key/OAuth2 validation, rate limiting, request shaping, and identity header propagation before traffic reaches services | Does not secure east-west service calls by itself; trusted headers require strict network boundaries |
| Service Mesh mTLS | Service-to-service traffic inside the cluster, especially across teams or sensitive domains | Workload identity, automatic mTLS, peer authentication, and proxy-level authorization policy | Adds data-plane/control-plane complexity and operational coupling to sidecars or ambient mesh components |
| OAuth2 Proxy | Browser-facing internal apps that need OIDC login, redirects, cookies, and session handling | Delegates login/session management to a reverse proxy and forwards authenticated identity headers | Best for HTTP/browser flows; not a general machine-to-machine authorization system |
| OPA | Complex, auditable, frequently changing authorization rules | Separates policy decisions from application releases and can run as sidecar, service, ext_authz backend, or admission control via Gatekeeper | Policy/data distribution and failure behavior must be designed deliberately |
| SPIFFE/SPIRE | Multi-cluster, multi-cloud, or meshless environments that need portable workload identity | Issues short-lived workload identities without application-managed shared secrets | Provides 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.comOnce 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: MINUTESGravitee’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-serviceThis 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: falseThe 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: 8080The 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-policyAs 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 Gatekeeper — OPA 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-serviceServices 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: DirectoryOrCreateSPIFFE 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:
| Layer | Tool | Addresses |
|---|---|---|
| Edge (north-south) | API Gateway (Gravitee, Emissary, AWS APIGW) | External clients → cluster |
| Browser sessions | OAuth2 Proxy | Browser-facing apps with login flows |
| East-west mTLS | Service Mesh (Istio/Linkerd) + SPIFFE | Service-to-service identity |
| Policy | OPA/OPAL | Fine-grained, auditable authorization |
| Workload identity | SPIRE | Multi-cloud/multi-cluster identity |
A production deployment at reasonable scale looks like:
- External traffic hits an API Gateway or Ingress controller with OAuth2 Proxy
- The gateway validates the token, strips it, and forwards identity headers to the upstream service
- Inside the cluster, all service-to-service calls are mTLS via a service mesh, using SPIFFE workload identity
- Authorization decisions (beyond identity) are delegated to OPA
- 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 webhookWhat 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 Microservices – NIST SP 800-204A: Building Secure Microservices-based Applications Using Service-Mesh Architecture – NIST SP 800-207: Zero Trust Architecture – OWASP Kubernetes Security Cheat Sheet – OWASP Microservices Security Cheat Sheet – CNCF Cloud Native Security Whitepaper v2 – RFC 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 Madden – CVE-2023-2728: Kubernetes mountable secrets policy bypass – CVE-2023-2727: Kubernetes ImagePolicyWebhook bypass – Auth0: Critical Vulnerabilities in JSON Web Token Libraries (alg:none, RS/HS confusion) – PortSwigger Web Security Academy: JWT attacks – jwt.io: Debugger and library reference
Tools and Projects – OAuth2 Proxy (GitHub — 14k+ stars) – OAuth2 Proxy Documentation – Gravitee.io API Gateway — JWT Policy – Gravitee Kubernetes Operator – Gravitee Helm Chart – Emissary-Ingress AuthService – OPA Gatekeeper – Istio Security: RequestAuthentication and AuthorizationPolicy – Envoy External Authorization Filter – Linkerd Server Policy – Open Policy Agent – OPAL — Open Policy Administration Layer – SPIFFE — Secure Production Identity Framework for Everyone – SPIRE — SPIFFE Runtime Environment – Netflix Zuul (GitHub) – Traefik ForwardAuth Middleware
Architecture and Industry Context – Google BeyondCorp: A New Approach to Enterprise Security – Google BeyondCorp Research Paper (USENIX ;login:) – Netflix Tech Blog: Zuul 2 — The Netflix Journey to Asynchronous, Non-Blocking Systems – SPIFFE/SPIRE CNCF Graduation Announcement – OPA CNCF Graduation – InfoQ: Microservices Authentication and Authorization Anti-Patterns – AWS re:Invent: Zero Trust Networking on AWS – Istio Service Mesh Security Architecture – The 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
- NVD: CVE-2022-21449
- MITRE/CVE: CVE-2022-21449
- NVD: CVE-2023-2728
- MITRE/CVE: CVE-2023-2728
- Kubernetes Security Advisory: CVE-2023-2728
- Kubernetes Issue: CVE-2023-2727 and CVE-2023-2728
- Auth0: Critical Vulnerabilities in JSON Web Token Libraries
- RFC 8725: JSON Web Token Best Current Practices
- Istio RequestAuthentication reference
- Istio AuthorizationPolicy reference
- ingress-nginx external OAuth authentication example
- OAuth2 Proxy configuration overview
- SPIFFE/SPIRE Kubernetes quickstart
- SPIFFE/SPIRE install agents documentation
- Uber Engineering: The Architecture of Uber’s API Gateway
- Netflix Zuul GitHub repository
- Netflix Tech Blog: Announcing Zuul
- Netflix Tech Blog: Zuul 2