fix(middleware): forward configured headers on SSE/WebSocket bypass (#162)

The SSE and WebSocket bypass forwarded only X-Forwarded-User. It did not
apply the configured headers. Backends that authenticate the proxy with a
shared-secret header returned 401 on WebSocket upgrades. Version 0.8.x sent
WebSocket upgrades through the normal flow, so this is a regression.

The bypass now renders the header templates through applyHeaderTemplates,
which forwardAuthorized also uses. Templates use the ID-token claims from
the session cookie. If the session has no ID token, they use the
access-token claims.

The bypass now also rejects a session that the IdP logged out through
back-channel or front-channel logout. Without this check, a logged-out
cookie can open streams with the proxy secret attached.

Add a yaegi check for the WebSocket case. Move tokenWithGroups to the
shared JWT test helpers, as FIX-41 requires.

Claude-Session: https://claude.ai/code/session_01G1d1n1BrHHHw8dBUkbiBPC
This commit is contained in:
2026-09-28 15:24:33 +01:00
parent 45e99551f9
commit c9844d0ff2
6 changed files with 468 additions and 93 deletions
-20
View File
@@ -2,8 +2,6 @@ package traefikoidc
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
@@ -64,24 +62,6 @@ func TestIsRetryableError_HTTPErrorStatus(t *testing.T) {
// --- R118 fix C: bypass role gate must fall back to access-token claims ---
// tokenWithGroups builds a JWT whose signature segment is long enough to pass
// the session chunk manager's format validation (>=10 chars) so the access
// token round-trips through Set/GetAccessToken.
func tokenWithGroups(t *testing.T, claims map[string]interface{}) string {
t.Helper()
hdr, _ := json.Marshal(map[string]string{"alg": "RS256", "typ": "JWT"})
pl, _ := json.Marshal(claims)
// Varied (non-repeating) signature bytes so the session chunk manager's
// repeated-character heuristic does not reject it.
sigBytes := make([]byte, 32)
for i := range sigBytes {
sigBytes[i] = byte(i + 1)
}
sig := base64.RawURLEncoding.EncodeToString(sigBytes)
return base64.RawURLEncoding.EncodeToString(hdr) + "." +
base64.RawURLEncoding.EncodeToString(pl) + "." + sig
}
// TestApplyBypassUserHeaders_AccessTokenGroupFallback regresses R118: the
// SSE/WebSocket bypass role gate read only the session's ID token, while the
// main authorization path falls back to access-token claims when no ID token