Auth as a configurable sidecar
Based on ThoughtWorks' Authentication Sidecar pattern: move authentication itself off the gateway and into a per-workload sidecar, so no service needs a language-specific OAuth SDK. Two independent halves — an ingress sidecar that verifies inbound tokens, and an egress sidecar that acquires outbound service-to-service tokens. Try both below.
- • Gateway verifies + authorizes, one place
- • Simple mental model, single hop
- • Gateway still verifies the token
- • App's own sidecar decides authZ
- • Gateway does routing only — Infra never has to own auth
- • Every sidecar is the same deployable — patch once, roll out everywhere
- • You're mid-strangler-migration. Peeling auth off a legacy service one workload at a time needs a seam that doesn't require the whole fleet to move at once — the sidecar is that seam.
- • Infra owns the gateway, not auth. If the team that runs the shared gateway doesn't want to (or isn't equipped to) carry auth-specific config, routing-only stays their job and auth becomes a bounded unit Engineering owns end to end.
- • You need a fleet-wide upgrade lever. Every service's sidecar is the same container image / Helm chart — rotating a library version or patching a CVE is one coordinated rollout across every workload, not N separate per-service integration updates.
- • You can decouple auth from app code but can't reach the gateway. Multi-tenant shared infra, a gateway you don't control, or no single front door yet — the sidecar gets you most of the decoupling win without needing that authority.
1 Ingress — authentication moves off the gateway
Same login as every other demo (Keycloak ROPC, JWT verified against the same JWKS) — the difference is who verifies it. APISIX's route for /authsidecar-demo/* has no auth plugins at all — no openid-connect, no opa plugin. It's pure routing to an Ory Oathkeeper sidecar, which does 100% of the JWT/JWKS verification and forwards to the exact same sidecar_routes.py endpoints (and co-located OPA authZ) that the sidecar demo uses. Same app code, same authorization — only the component doing authentication changed.
Authenticate
Call Oathkeeper-verified endpoints
The gateway forwards these untouched. demo-api:4455 (Oathkeeper, reachable via demo-api's shared network namespace) verifies the token before anything else sees the request.
Request Log
No requests yet.
2 Egress — service-to-service, no person involved
No login here — clicking the button below is the demo: a service authenticating itself to another service. A separate egress-sidecar-demo-api container holds the m2m-demo-client secret — demo-api itself never sees it. It acquires a client_credentials token, caches it, and attaches it to an outbound call. That call goes right back through the same Oathkeeper ingress sidecar above — a real east-west hop through the same enforcement point, not a side door.
- What it costs
- Two more containers per workload (ingress + egress) on top of whatever else is already co-located there — more processes to run, and every workload pays it, not just the ones that need it. The egress sidecar's credential (here, a client_secret in an env var) is a real interim shortcut — the honest fix is workload identity (SPIFFE/SPIRE) so it proves who it is instead of holding a secret. That's a planned next step, not done here.
- When you'd choose differently
- Reach for the gateway or OPA-sidecar patterns when one team already owns the gateway end to end and there's no organizational reason to keep auth out of it — simpler mental model, no per-workload container tax. Reach for the authentication sidecar for the scenarios above, and especially for the egress half regardless of which ingress pattern you use elsewhere: uniform, auto-refreshing service-to-service tokens with zero per-service integration code has no equivalent in the gateway pattern at all.