{"record":{"id":"d22a262268cb9a69","repo":"gofr-dev/gofr","slug":"invalid-claim-structure","errorCode":null,"errorMessage":"invalid claim structure","messagePattern":"invalid claim structure","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gofr/rbac/middleware.go","lineNumber":74,"sourceCode":"\terrClaimPathNotFound = errors.New(\"claim path not found\")\n\n\t// errInvalidArrayNotation is returned when array notation is invalid.\n\terrInvalidArrayNotation = errors.New(\"invalid array notation\")\n\n\t// errInvalidArrayIndex is returned when array index is invalid.\n\terrInvalidArrayIndex = errors.New(\"invalid array index\")\n\n\t// errClaimKeyNotFound is returned when a claim key is not found.\n\terrClaimKeyNotFound = errors.New(\"claim key not found\")\n\n\t// errClaimValueNotArray is returned when a claim value is not an array.\n\terrClaimValueNotArray = errors.New(\"claim value is not an array\")\n\n\t// errArrayIndexOutOfBounds is returned when array index is out of bounds.\n\terrArrayIndexOutOfBounds = errors.New(\"array index out of bounds\")\n\n\t// errInvalidClaimStructure is returned when claim structure is invalid.\n\terrInvalidClaimStructure = errors.New(\"invalid claim structure\")\n\n\t// errAuthorizationError is returned as a generic error message for unknown errors in traces.\n\terrAuthorizationError = errors.New(\"authorization error\")\n)\n\n// Middleware creates an HTTP middleware function that enforces RBAC authorization.\n// It extracts the user's role and checks if the role is allowed for the requested route.\n//\n//nolint:gocognit,gocyclo // Middleware complexity is acceptable due to multiple authorization paths\nfunc Middleware(config *Config) func(handler http.Handler) http.Handler {\n\treturn func(handler http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t// If config is nil, allow all requests (fail open)\n\t\t\tif config == nil {\n\t\t\t\thandler.ServeHTTP(w, r)\n\n\t\t\t\treturn\n\t\t\t}","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/rbac/middleware.go#L56-L92","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (\"realm_access.roles\" is an array, not an object)\nconfig.JWTClaimPath = \"realm_access.roles.admin\"\n// after\nconfig.JWTClaimPath = \"realm_access.roles[0]\"","handlingStrategy":"validation","validationCode":"func pathNavigable(claims jwt.MapClaims, path string) bool {\n    var cur any = claims\n    for _, part := range strings.Split(path, \".\") {\n        part = strings.SplitN(part, \"[\", 2)[0]\n        m, ok := cur.(map[string]any)\n        if !ok { if mc, ok2 := cur.(jwt.MapClaims); ok2 { m, ok = mc, true } }\n        if !ok { return false }\n        v, exists := m[part]\n        if !exists { return false }\n        cur = v\n    }\n    return true\n}","typeGuard":"func isClaimMap(v any) bool {\n    switch v.(type) {\n    case map[string]any, jwt.MapClaims:\n        return true\n    }\n    return false\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"invalid claim structure\") {\n    logger.Warn(\"dot path traverses a non-object claim; use array notation\", \"err\", err)\n    http.Error(w, \"unauthorized\", http.StatusUnauthorized)\n    return\n}","preventionTips":["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"],"tags":["go","jwt","rbac","claims"],"backgroundTag":"jwt-claim-path-invalid","analyzedSha":"187eb24962502e91f1fee856230670958b66e89c","analyzedAt":"2026-09-01T20:34:54.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}