diff --git a/internal/auth/keycloak.go b/internal/auth/keycloak.go index 26fa344..53b95aa 100644 --- a/internal/auth/keycloak.go +++ b/internal/auth/keycloak.go @@ -11,8 +11,11 @@ package auth import ( "context" "crypto/tls" + "encoding/base64" + "encoding/json" "errors" "fmt" + "log" "net/http" "strings" "sync" @@ -162,6 +165,12 @@ func (vr *Verifier) Middleware() fiber.Handler { raw := strings.TrimPrefix(header, "Bearer ") claims, err := vr.Verify(c.UserContext(), raw) if err != nil { + // Temporary diagnostic — log the actual verify error and the + // token's header (kid, alg, typ) so we can tell signature vs + // expiration vs key-not-found apart. Payload claims (sub, iss, + // exp, aud) too so we can correlate with realm config. + log.Printf("[auth] verify failed: %v | header=%s payload=%s", + err, decodeJWTPart(raw, 0), decodeJWTPart(raw, 1)) return c.Status(fiber.StatusUnauthorized). JSON(fiber.Map{"error": fmt.Sprintf("invalid token: %v", err)}) } @@ -170,6 +179,39 @@ func (vr *Verifier) Middleware() fiber.Handler { } } +// decodeJWTPart base64-decodes header (idx 0) or payload (idx 1) of a JWT +// and pretty-prints selected fields. Best-effort — returns the raw segment +// on parse failure. Never returns the signature segment. +func decodeJWTPart(token string, idx int) string { + parts := strings.Split(token, ".") + if idx >= len(parts) || idx > 1 { + return "(missing)" + } + raw, err := base64.RawURLEncoding.DecodeString(parts[idx]) + if err != nil { + return parts[idx][:min(len(parts[idx]), 40)] + "(b64err)" + } + var m map[string]any + if err := json.Unmarshal(raw, &m); err != nil { + return string(raw)[:min(len(raw), 80)] + } + keep := map[string]any{} + for _, k := range []string{"kid", "alg", "typ", "iss", "aud", "azp", "sub", "exp", "iat"} { + if v, ok := m[k]; ok { + keep[k] = v + } + } + b, _ := json.Marshal(keep) + return string(b) +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + func User(c *fiber.Ctx) *UserClaims { if u, ok := c.Locals("user").(*UserClaims); ok { return u