gofr-dev/gofr · error
invalid claim structure
Error message
invalid claim structure
What it means
errInvalidClaimStructure is returned by extractNestedClaim when a dot-notation path traverses into a value that is neither map[string]any nor jwt.MapClaims at its final segment — i.e. the intermediate claim is a scalar/array but the path keeps navigating as if it were an object. The message includes the path prefix where the structure broke. It signals a mismatch between the configured dot path and the token's nested claim shape.
Source
Thrown at pkg/gofr/rbac/middleware.go:74
errClaimPathNotFound = errors.New("claim path not found")
// errInvalidArrayNotation is returned when array notation is invalid.
errInvalidArrayNotation = errors.New("invalid array notation")
// errInvalidArrayIndex is returned when array index is invalid.
errInvalidArrayIndex = errors.New("invalid array index")
// errClaimKeyNotFound is returned when a claim key is not found.
errClaimKeyNotFound = errors.New("claim key not found")
// errClaimValueNotArray is returned when a claim value is not an array.
errClaimValueNotArray = errors.New("claim value is not an array")
// errArrayIndexOutOfBounds is returned when array index is out of bounds.
errArrayIndexOutOfBounds = errors.New("array index out of bounds")
// errInvalidClaimStructure is returned when claim structure is invalid.
errInvalidClaimStructure = errors.New("invalid claim structure")
// errAuthorizationError is returned as a generic error message for unknown errors in traces.
errAuthorizationError = errors.New("authorization error")
)
// Middleware creates an HTTP middleware function that enforces RBAC authorization.
// It extracts the user's role and checks if the role is allowed for the requested route.
//
//nolint:gocognit,gocyclo // Middleware complexity is acceptable due to multiple authorization paths
func Middleware(config *Config) func(handler http.Handler) http.Handler {
return func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// If config is nil, allow all requests (fail open)
if config == nil {
handler.ServeHTTP(w, r)
return
}View on GitHub (pinned to 187eb24962)
Solutions
- Decode the token and check the type at each path segment; use array notation "key[0]" where the value is a list instead of dot notation.
- Correct JWTClaimPath so every intermediate segment is a JSON object, e.g. "resource_access.myclient.roles[0]" for nested Keycloak claims.
- Combine notations correctly: array index and dot segments can appear in one path ("a.b[0]"), but a dot after an array element is only valid if that element is itself an object.
- For mixed shapes, normalize the token or handle both in a custom ErrorHandler.
Example fix
// before ("realm_access.roles" is an array, not an object)
config.JWTClaimPath = "realm_access.roles.admin"
// after
config.JWTClaimPath = "realm_access.roles[0]" Defensive patterns
Strategy: validation
Validate before calling
func pathNavigable(claims jwt.MapClaims, path string) bool {
var cur any = claims
for _, part := range strings.Split(path, ".") {
part = strings.SplitN(part, "[", 2)[0]
m, ok := cur.(map[string]any)
if !ok { if mc, ok2 := cur.(jwt.MapClaims); ok2 { m, ok = mc, true } }
if !ok { return false }
v, exists := m[part]
if !exists { return false }
cur = v
}
return true
} Type guard
func isClaimMap(v any) bool {
switch v.(type) {
case map[string]any, jwt.MapClaims:
return true
}
return false
} Try / catch
if err != nil && strings.Contains(err.Error(), "invalid claim structure") {
logger.Warn("dot path traverses a non-object claim; use array notation", "err", err)
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
} Prevention
- Use array notation [i] wherever the claim is a JSON array; dot notation only for objects
- Mirror the exact nested structure from a decoded token (e.g. realm_access.roles[0])
- Re-check paths after IdP or claim-shape migrations
When it happens
Trigger: JWTClaimPath like "roles.admin" where "roles" is an array ([]any) rather than a map — the switch hits the default branch at the last segment and returns errInvalidClaimStructure; also "name.first" where "name" is a plain string.
Common situations: Path written assuming a nested object while the IdP emits an array of roles (needing "roles[0]" notation instead); mixing array and dot notation incorrectly (dot notation cannot index arrays); migrating between IdPs with differently shaped role claims; typos where the last segment should have been a key inside a map.
Related errors
- claim key not found
- claim value is not an array
- array index out of bounds
- failed to extract role from JWT: %w
- %w: %s
AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01).
Data as JSON: /api/errors/d22a262268cb9a69.
Report an issue: GitHub.