Every company has them: the internal dashboard with a shared password, the admin panel that trusts anyone on the VPN, the reporting tool whose vendor stopped shipping updates years ago. Nobody wants to add an OpenID Connect library to them, and for some there is no source code to add it to. They still need single sign-on, group-based access and a record of who opened what.
The way out is to authenticate the request before it reaches the application. This post explains how that works when the interception happens in the kernel with eBPF, and the header-handling rules that decide whether it is secure.
The idea: authenticate in front of the app
An application does not need to run a sign-in flow if something in front of it already has. The pattern is old: an identity-aware reverse proxy checks for a session, redirects the browser to the identity provider when there is none, and forwards the request once the user is known.
What has changed is where the proxy can sit. A classic reverse proxy needs traffic routed to it: a DNS change, an ingress rule, or a sidecar container injected into every pod. eBPF allows a different placement. Small programs attached inside the Linux kernel of each node see connections as they arrive and can hand the ones bound for a protected service to an agent on the same node. The application keeps listening on the port it always did. Nothing is added to its pod and nothing in its configuration changes.
What happens on a request
A person in a browser
- The browser requests
GET /dashboardon the application's hostname. - On the node, the kernel program recognises the destination as a protected service and hands the connection to the node agent.
- The agent finds no session and answers with a redirect to the sign-in page.
- The person signs in with their normal account, including a second step if policy asks for one.
- The browser returns through a callback and receives a session cookie scoped to that one application.
- The original request is forwarded to the application, now carrying identity headers.
A program calling an API
There is no redirect for a machine client. It presents an access token, and the agent validates it: signature, issuer, expiry, and that the token was issued for this API. A token issued for a different API is refused before the service sees it.
In both cases the application only ever receives requests from callers that passed.
Identity headers: how the app learns who is calling
An application that has never heard of your identity provider still needs to know who the user is, at least to show a name or apply its own roles. The proxy tells it with request headers:
GET /dashboard HTTP/1.1
Host: wiki.internal.example
X-Auth-User-Id: 6f1c2a9e-0b7d-4c1e-9a35-2d8f4e6b7c10
X-Auth-User-Email: dev@example.comMany older applications already support this style of integration under names such as "remote user" or "trusted header" authentication. Reading one header is usually a configuration switch, not a code change.
Why inbound copies must be stripped
Here is the rule that matters most: a header that carries trust must be deleted from the incoming request before the proxy sets its own.
Consider what happens without it. A caller sends:
GET /admin HTTP/1.1
X-Auth-User-Email: ceo@example.comIf the proxy authenticates the caller as an ordinary user and then appends its own headers while leaving the caller's in place, the application may see two values, or just the attacker's. Which one it reads depends on its framework. Impersonating anyone becomes a matter of typing a header.
So the boundary must do two things in order:
- Remove every inbound header in the trusted namespace, whatever its value and whatever its capitalisation. Header names are case-insensitive.
- Stamp fresh values derived from the session or token it just verified.
A header accepted from the caller is not a trust assertion at all.
The second half: nothing may reach the app except through the boundary
Stripping only helps if the application cannot be reached by another path. If a neighbouring pod can connect straight to the application's port and set the headers itself, the proxy has been walked around. This is where kernel-level interception has an advantage over a proxy at the cluster edge: the check happens on the node the service runs on, for connections arriving at that service, not only for traffic that came through the front door.
Signed assertions: when headers are not enough
Plain headers rely on the network path being trustworthy. Where an application can verify a signature, a stronger option is for the proxy to pass a signed assertion: a short-lived JWT, signed with a key the application can fetch from a published key set, containing the user, the audience and an expiry. The application checks the signature and audience itself, so a forged header from inside the network fails.
It has a cost, which is exactly what zero-code set out to avoid: the application needs code to verify the token. A reasonable position is plain headers plus a tightly enforced network boundary for applications you cannot change, and signed assertions for those you can.
Limits worth knowing before you start
Authenticating in front of an application gives it sign-in and coarse access control. It does not give the application new behaviour.
- The app's own authorisation model is untouched. Route rules by group help, but object-level permissions stay in the app.
- Logout needs thought: ending the proxy session does not end a session the app keeps itself.
- Kernel-level inspection reads HTTP it can see. Traffic that is encrypted end to end inside the cluster is opaque to it.
For when to prefer a full integration, see what is zero-code authentication.
How AuthFI does it
AuthFI's Zero-Code is one install on a Kubernetes cluster. The checks are eBPF programs loaded into the kernel of each node, with one agent beside them; nothing is added to pods and there is no sidecar.
Putting a service on a hostname registers it as an application, and you assign groups to it exactly as you would for an app integrated by hand. A person in a browser is sent to your sign-in page and brought back with a session for that one application. A program's token must be a valid access token issued for that API. Rules on a route say which roles and groups may call it and whether it needs a second step.
The proxy stamps identity headers onto the requests it forwards into the application: X-AuthFI-User-ID, X-AuthFI-User-Email, X-AuthFI-Agent-ID, X-AuthFI-Token-Type and X-AuthFI-On-Behalf-Of. Every inbound x-authfi-* header is deleted before stamping, so a caller cannot supply its own.
Stated limits: Kubernetes on Linux, x86. It reads plain HTTP as seen on the node. The sign-in redirect needs the standard kube-proxy mode; on Cilium's replacement the node can refuse traffic but cannot ask for sign-in. Health checks, sign-in callbacks and a short fixed list of public paths are always let through. Every rule starts in watch-only, and nothing is refused until you switch it on.
Key takeaways
- You can give an unmodifiable app single sign-on by authenticating the request before it arrives.
- eBPF lets the interception happen on the node, with no sidecar and no change to the pod.
- Always strip inbound identity headers before stamping your own, and make sure the app cannot be reached around the boundary.
- Use signed assertions where the app can verify them; accept plain headers only behind an enforced boundary.



