Adding SSO to your applications with Traefik’s OAuth2 proxy is surprisingly simple, but the real magic is that it doesn’t require any application code changes whatsoever.
Let’s see it in action. Imagine we have a simple internal web app, myapp.internal.local, that we want to protect. We’ll use Authelia as our OAuth2 provider (though any OpenID Connect compliant provider works).
Here’s a snippet of a Traefik configuration that achieves this:
http:
routers:
myapp-router:
rule: "Host(`myapp.internal.local`)"
entryPoints:
- "websecure"
service: "myapp-service"
middlewares:
- "authelia-auth" # This is where the magic happens
services:
myapp-service:
loadBalancer:
servers:
- url: "http://192.168.1.100:8080" # Your actual application backend
middlewares:
authelia-auth:
forwardAuth:
address: "http://authelia.internal.local/api/verify" # Your Authelia instance
authResponseHeaders:
- "X-Forwarded-User"
- "X-Forwarded-Email"
- "X-Forwarded-Groups"
When a user navigates to myapp.internal.local, Traefik intercepts the request. It doesn’t know about myapp-service yet; instead, it sends the request to the authelia-auth middleware. This middleware, configured with forwardAuth, forwards the request to our Authelia instance at authelia.internal.local/api/verify.
Authelia checks if the user is authenticated. If not, it redirects them to its login page. After a successful login, Authelia redirects the user back to myapp.internal.local, but this time, it includes specific headers in the request like X-Forwarded-User and X-Forwarded-Email. Traefik receives this authenticated request from Authelia and then forwards it to the actual myapp-service. Crucially, it strips the Authelia authentication headers and adds the user information headers to the request sent to your application. Your application, blissfully unaware of any OAuth2 dance, simply sees a request with headers indicating who the user is.
This whole process effectively decouples authentication from your application logic. Your application just needs to be configured to trust and read the X-Forwarded-User (or similar) header. Traefik acts as the gatekeeper and translator, handling the OAuth2 flow with your chosen identity provider and then presenting the user’s identity to your application in a simple, consumable format.
The core problem this solves is adding robust, modern authentication to legacy or simple applications without touching their codebase. Instead of embedding OAuth2 client libraries or writing custom authentication logic, you simply place Traefik in front and configure a middleware. This is a massive time-saver and significantly reduces the attack surface of your applications by centralizing authentication.
Internally, the forwardAuth middleware in Traefik works by making an HTTP request to the specified address (your identity provider’s verification endpoint). It passes the original request’s headers and body to this endpoint. The identity provider processes this, determines if the user is authenticated and authorized, and then responds. If the response indicates authentication success, the identity provider can optionally include headers in its response. Traefik then takes these headers and adds them to the original request before forwarding it to the backend service. If the identity provider’s response indicates failure, Traefik typically returns an unauthorized (401) or forbidden (403) error to the client.
The authResponseHeaders parameter is key here. It tells Traefik which headers from the identity provider’s response to keep and forward to your backend service. This is how user identity information (like username, email, or group memberships) is communicated. Without this, the backend service would just see a request that Traefik deemed "authenticated" but wouldn’t know who the authenticated user was.
A common point of confusion is understanding that your application doesn’t need to know how the headers got there. It just needs to be configured to read them. For example, an application written in Go might read r.Header.Get("X-Forwarded-User"). The beauty is that this configuration is external to the application itself, often done at the web server or load balancer level if Traefik isn’t directly serving the application.
The actual authResponseHeaders that your identity provider sends back are determined by its own configuration. For Authelia, you’d configure it to include these headers in its responses when it successfully verifies a request. The exact format and names of these headers are flexible but must match between your identity provider’s verification endpoint response and Traefik’s authResponseHeaders configuration.
The most surprising mechanical detail is that Traefik, by default, does not automatically pass the original Authorization header from the client to the forwardAuth endpoint. If your identity provider relies on an Authorization header for its verification process (e.g., a bearer token), you’ll need to explicitly tell Traefik to pass it through using passHeaders within the forwardAuth configuration.
Once you have your applications protected, the next logical step is to implement session management and potentially user-facing portals for managing accounts and access.