From 811046c4ce305444b9bc9b3c2e3dfb527e10f7e5 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Mon, 4 May 2026 03:24:34 -0700 Subject: [PATCH] auth/oidc: support slice-typed extra claims via overlap match (#1238) Closes #988. Direct `actualValue != expectedValue` fails when the token claim is an array (groups, roles, scp, aud). Switch to a slice-aware any-of overlap so OIDC list claims are validated correctly. --- internal/api/handlers/v0/auth/oidc.go | 34 ++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/internal/api/handlers/v0/auth/oidc.go b/internal/api/handlers/v0/auth/oidc.go index 0cef699..98526e1 100644 --- a/internal/api/handlers/v0/auth/oidc.go +++ b/internal/api/handlers/v0/auth/oidc.go @@ -224,7 +224,7 @@ func (h *OIDCHandler) validateExtraClaims(claims *OIDCClaims) error { return fmt.Errorf("claim validation failed: required claim %s not found", key) } - if actualValue != expectedValue { + if !claimMatches(actualValue, expectedValue) { return fmt.Errorf("claim validation failed: %s expected %v, got %v", key, expectedValue, actualValue) } } @@ -233,6 +233,38 @@ func (h *OIDCHandler) validateExtraClaims(claims *OIDCClaims) error { return nil } +// claimMatches reports whether a claim value satisfies the expected configuration. +// Both sides may be a scalar or a slice; for slice-typed claims (groups, roles, scp, +// aud, etc.) any single overlap is treated as a match, mirroring how OIDC consumers +// normally interpret list claims. +func claimMatches(actual, expected any) bool { + actualList := toAnySlice(actual) + expectedList := toAnySlice(expected) + for _, e := range expectedList { + for _, a := range actualList { + if a == e { + return true + } + } + } + return false +} + +func toAnySlice(v any) []any { + switch s := v.(type) { + case []any: + return s + case []string: + out := make([]any, len(s)) + for i, x := range s { + out[i] = x + } + return out + default: + return []any{v} + } +} + // buildPermissions builds permissions based on OIDC claims and configuration func (h *OIDCHandler) buildPermissions(_ *OIDCClaims) []auth.Permission { var permissions []auth.Permission