Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

Commit 4e4ee1f

Browse files
committed
Have a seperate endpoint for traefik auth
1 parent 01a625d commit 4e4ee1f

6 files changed

Lines changed: 246 additions & 98 deletions

File tree

README.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -163,18 +163,17 @@ go run main.go
163163

164164
### Auth Endpoint Behavior
165165

166-
The `/auth` endpoint automatically adapts to work with both Nginx and Traefik ingress controllers:
166+
The auth service provides separate endpoints for different ingress controllers:
167167

168-
**For Nginx auth_request:**
169-
- Authenticated users: Returns `200 OK` with user headers
168+
**Nginx auth_request (`/auth/nginx`):**
169+
- Authenticated users: Returns `200 OK` with user headers (`X-Auth-User`, `X-Auth-User-ID`)
170170
- Unauthenticated users: Returns `401 Unauthorized`
171171

172-
**For Traefik ForwardAuth:**
173-
- Authenticated users: Returns `200 OK` with user headers
174-
- Unauthenticated users (with redirect param): Returns `302 Found` with `Location` header pointing to login page
175-
- Unauthenticated users (without redirect param): Returns `401 Unauthorized` (fallback for Nginx)
172+
**Traefik ForwardAuth (`/auth/traefik`):**
173+
- Authenticated users: Returns `200 OK` with user headers (`X-Auth-User`, `X-Auth-User-ID`)
174+
- Unauthenticated users: Returns `302 Found` with `Location` header pointing to login page
176175

177-
The endpoint detects the ingress controller type by checking for query parameters like `rd` or `redirect` that Traefik typically includes.
176+
The Traefik endpoint automatically reconstructs the original URL from forwarded headers (`X-Forwarded-Host`, `X-Forwarded-Uri`, `X-Forwarded-Proto`) to provide proper redirect functionality.
178177

179178
### Key API Endpoints
180179

@@ -184,11 +183,10 @@ The endpoint detects the ingress controller type by checking for query parameter
184183
| `/api/register/finish` | POST | Complete passkey registration |
185184
| `/api/login/begin` | POST | Start passkey authentication |
186185
| `/api/login/finish` | POST | Complete passkey authentication |
187-
| `/auth` | GET | Auth check endpoint for ingress controllers |
186+
| `/auth/nginx` | GET | Auth check endpoint for Nginx auth_request |
187+
| `/auth/traefik` | GET | Auth check endpoint for Traefik ForwardAuth |
188188
| `/api/users` | GET/POST | List/create users |
189189
| `/health` | GET | Health check |
190-
191-
192190
## 📄 License
193191

194192
Apache License 2.0

helm/passkey-auth/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ kind: Ingress
9595
metadata:
9696
name: my-protected-app
9797
annotations:
98-
nginx.ingress.kubernetes.io/auth-url: "https://auth.example.com/auth"
98+
nginx.ingress.kubernetes.io/auth-url: "https://auth.example.com/auth/nginx"
9999
nginx.ingress.kubernetes.io/auth-signin: "https://auth.example.com/login?rd=$scheme://$http_host$request_uri"
100-
nginx.ingress.kubernetes.io/auth-response-headers: "X-Auth-User,X-Auth-Email"
100+
nginx.ingress.kubernetes.io/auth-response-headers: "X-Auth-User,X-Auth-User-ID"
101101
spec:
102102
# ... your ingress spec
103103
```
@@ -114,7 +114,7 @@ metadata:
114114
namespace: your-app-namespace
115115
spec:
116116
forwardAuth:
117-
address: https://auth.example.com/auth
117+
address: https://auth.example.com/auth/traefik
118118
authRequestHeaders:
119119
- "X-Forwarded-Method"
120120
- "X-Forwarded-Proto"
@@ -123,8 +123,8 @@ spec:
123123
- "X-Forwarded-For"
124124
authResponseHeaders:
125125
- "X-Auth-User"
126-
- "X-Auth-Email"
127-
authResponseHeadersRegex: "^X-"
126+
- "X-Auth-User-ID"
127+
authResponseHeadersRegex: "^X-|^Location$"
128128
---
129129
apiVersion: networking.k8s.io/v1
130130
kind: Ingress

helm/passkey-auth/templates/traefik-middleware.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ metadata:
88
{{- include "passkey-auth.labels" . | nindent 4 }}
99
spec:
1010
forwardAuth:
11-
address: http{{ if .Values.ingress.tls }}s{{ end }}://{{ (index .Values.ingress.hosts 0).host }}/auth
11+
address: http{{ if .Values.ingress.tls }}s{{ end }}://{{ (index .Values.ingress.hosts 0).host }}/auth/traefik
1212
authRequestHeaders:
1313
- X-Forwarded-Method
1414
- X-Forwarded-Proto

internal/handlers/handlers.go

Lines changed: 100 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -392,76 +392,137 @@ func (h *Handlers) Logout(w http.ResponseWriter, r *http.Request) {
392392
h.writeJSON(w, map[string]string{"status": "success"})
393393
}
394394

395-
// AuthCheck implements auth backend for both nginx auth_request and Traefik ForwardAuth
396-
func (h *Handlers) AuthCheck(w http.ResponseWriter, r *http.Request) {
397-
// Debug logging
398-
logrus.Debugf("AuthCheck request from %s", r.RemoteAddr)
399-
logrus.Debugf("AuthCheck headers: %+v", r.Header)
400-
logrus.Debugf("AuthCheck cookies: %+v", r.Cookies())
401-
logrus.Debugf("AuthCheck query params: %+v", r.URL.Query())
395+
// AuthCheckNginx implements nginx auth_request protocol
396+
func (h *Handlers) AuthCheckNginx(w http.ResponseWriter, r *http.Request) {
397+
logrus.Debugf("Nginx AuthCheck request from %s", r.RemoteAddr)
398+
logrus.Debugf("Nginx AuthCheck headers: %+v", r.Header)
399+
logrus.Debugf("Nginx AuthCheck cookies: %+v", r.Cookies())
402400

403401
session, err := h.store.Get(r, "auth-session")
404402
if err != nil {
405403
logrus.Errorf("Failed to get auth session: %v", err)
406-
h.handleUnauthenticated(w, r)
404+
w.WriteHeader(http.StatusUnauthorized)
407405
return
408406
}
409407

410408
authenticated, ok := session.Values["authenticated"].(bool)
411409
logrus.Debugf("Session authenticated: %v, ok: %v", authenticated, ok)
412-
logrus.Debugf("Session values: %+v", session.Values)
413410

414411
if !ok || !authenticated {
415-
logrus.Debugf("User not authenticated")
416-
h.handleUnauthenticated(w, r)
412+
logrus.Debugf("User not authenticated, returning 401 for Nginx")
413+
w.WriteHeader(http.StatusUnauthorized)
417414
return
418415
}
419416

420-
// Optional: Add user info to response headers
417+
// Add user info to response headers for nginx
421418
if userID, ok := session.Values["user_id"].(int); ok {
422419
w.Header().Set("X-Auth-User-ID", strconv.Itoa(userID))
423420
}
424421
if userEmail, ok := session.Values["user_email"].(string); ok {
425422
w.Header().Set("X-Auth-User", userEmail)
426423
}
427424

428-
logrus.Debugf("User authenticated, returning 200")
425+
logrus.Debugf("User authenticated, returning 200 for Nginx")
429426
w.WriteHeader(http.StatusOK)
430427
}
431428

432-
// handleUnauthenticated handles unauthenticated requests for both Nginx and Traefik
433-
func (h *Handlers) handleUnauthenticated(w http.ResponseWriter, r *http.Request) {
434-
// Check for redirect parameter (Traefik ForwardAuth typically includes this)
435-
redirectURL := r.URL.Query().Get("rd")
436-
if redirectURL == "" {
437-
// Also check for other common redirect parameter names
438-
redirectURL = r.URL.Query().Get("redirect")
429+
// AuthCheckTraefik implements Traefik ForwardAuth protocol
430+
func (h *Handlers) AuthCheckTraefik(w http.ResponseWriter, r *http.Request) {
431+
logrus.Debugf("Traefik AuthCheck request from %s", r.RemoteAddr)
432+
logrus.Debugf("Traefik AuthCheck headers: %+v", r.Header)
433+
logrus.Debugf("Traefik AuthCheck cookies: %+v", r.Cookies())
434+
435+
session, err := h.store.Get(r, "auth-session")
436+
if err != nil {
437+
logrus.Errorf("Failed to get auth session: %v", err)
438+
h.handleTraefikUnauthenticated(w, r)
439+
return
440+
}
441+
442+
authenticated, ok := session.Values["authenticated"].(bool)
443+
logrus.Debugf("Session authenticated: %v, ok: %v", authenticated, ok)
444+
445+
if !ok || !authenticated {
446+
logrus.Debugf("User not authenticated")
447+
h.handleTraefikUnauthenticated(w, r)
448+
return
439449
}
440450

441-
// If redirect parameter is present, return 302 redirect (Traefik ForwardAuth)
451+
// Add user info to response headers for Traefik
452+
if userID, ok := session.Values["user_id"].(int); ok {
453+
w.Header().Set("X-Auth-User-ID", strconv.Itoa(userID))
454+
}
455+
if userEmail, ok := session.Values["user_email"].(string); ok {
456+
w.Header().Set("X-Auth-User", userEmail)
457+
}
458+
459+
logrus.Debugf("User authenticated, returning 200 for Traefik")
460+
w.WriteHeader(http.StatusOK)
461+
}
462+
463+
// handleTraefikUnauthenticated handles unauthenticated requests for Traefik ForwardAuth
464+
func (h *Handlers) handleTraefikUnauthenticated(w http.ResponseWriter, r *http.Request) {
465+
// Construct the original URL from Traefik headers
466+
redirectURL := h.constructOriginalURL(r)
467+
468+
logrus.Debugf("Constructed redirect URL from headers: %s", redirectURL)
469+
470+
// Construct login URL with redirect parameter
471+
loginURL := "/login.html"
442472
if redirectURL != "" {
443-
logrus.Debugf("Redirect parameter found (%s), returning 302 redirect for Traefik", redirectURL)
444-
445-
// Construct login URL with redirect parameter
446-
loginURL := "/login.html?redirect=" + redirectURL
447-
448-
// If we have a host header, construct a full URL
449-
if host := r.Header.Get("Host"); host != "" {
450-
scheme := "http"
451-
if r.Header.Get("X-Forwarded-Proto") == "https" || r.TLS != nil {
452-
scheme = "https"
453-
}
454-
loginURL = scheme + "://" + host + loginURL
473+
loginURL += "?redirect=" + redirectURL
474+
}
475+
476+
// If we have a host header, construct a full URL
477+
if host := r.Header.Get("Host"); host != "" {
478+
scheme := "http"
479+
if r.Header.Get("X-Forwarded-Proto") == "https" || r.TLS != nil {
480+
scheme = "https"
455481
}
482+
loginURL = scheme + "://" + host + loginURL
483+
}
456484

457-
w.Header().Set("Location", loginURL)
458-
w.WriteHeader(http.StatusFound) // 302
459-
return
485+
logrus.Debugf("Returning 302 redirect to: %s", loginURL)
486+
w.Header().Set("Location", loginURL)
487+
w.WriteHeader(http.StatusFound) // 302
488+
}
489+
490+
// constructOriginalURL reconstructs the original URL from Traefik forwarded headers
491+
func (h *Handlers) constructOriginalURL(r *http.Request) string {
492+
// Traefik sets various headers that we can use to reconstruct the original URL
493+
494+
// Check for X-Forwarded-Host (original host)
495+
host := r.Header.Get("X-Forwarded-Host")
496+
if host == "" {
497+
host = r.Header.Get("X-Original-Host")
498+
}
499+
if host == "" {
500+
// Fallback to Host header
501+
host = r.Header.Get("Host")
502+
}
503+
504+
// Check for X-Forwarded-Uri (original path + query)
505+
uri := r.Header.Get("X-Forwarded-Uri")
506+
if uri == "" {
507+
uri = r.Header.Get("X-Original-URI")
508+
}
509+
if uri == "" {
510+
// Fallback to request URI
511+
uri = r.RequestURI
512+
}
513+
514+
// Determine scheme
515+
scheme := "http"
516+
if r.Header.Get("X-Forwarded-Proto") == "https" || r.TLS != nil {
517+
scheme = "https"
518+
}
519+
520+
if host == "" {
521+
return ""
460522
}
461523

462-
// No redirect parameter, return 401 (Nginx auth_request)
463-
logrus.Debugf("No redirect parameter, returning 401 for Nginx auth_request")
464-
w.WriteHeader(http.StatusUnauthorized)
524+
// Construct the full URL
525+
return scheme + "://" + host + uri
465526
}
466527

467528
// GetAuthStatus returns the current authentication status

0 commit comments

Comments
 (0)