{"record":{"id":"d3ad906735a16a14","repo":"gofr-dev/gofr","slug":"claim-value-is-not-an-array","errorCode":null,"errorMessage":"claim value is not an array","messagePattern":"claim value is not an array","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gofr/rbac/middleware.go","lineNumber":68,"sourceCode":"\terrJWTClaimsNotFound = errors.New(\"JWT claims not found in request context\")\n\n\t// errEmptyClaimPath is returned when claim path is empty.\n\terrEmptyClaimPath = errors.New(\"empty claim path\")\n\n\t// errClaimPathNotFound is returned when a claim path is not found in JWT claims.\n\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) {","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/rbac/middleware.go#L50-L86","documentation":"errClaimValueNotArray is returned by extractArrayClaim when the claim exists in the JWT but its value is not a JSON array ([]any). Array notation like \"roles[0]\" only works when the claim holds a list; a string, map, or scalar at that key triggers this error. It indicates the claim-path shape does not match the token's actual claim structure.","triggerScenarios":"JWTClaimPath \"roles[0]\" while the token contains \"roles\": \"admin\" (a plain string) or \"roles\": {\"admin\": true} (a map); extractArrayClaim's type assertion value.([]any) fails and the error is returned with the offending key name.","commonSituations":"Identity provider emits role as a single string for users with one role but as an array for multiple roles; switching IdPs where the equivalent claim is a map of role->bool (Keycloak realm_access.roles vs resource_access); path written for one token shape reused against another client's tokens.","solutions":["Inspect the decoded token and align JWTClaimPath with the actual value type: use a plain key path (\"role\") for strings, or navigate maps with dot notation (\"realm_access.roles[0]\").","Ask the identity provider to always emit the claim as an array (multi-valued mapper) so the shape is stable.","If the claim can legitimately be either string or array, handle both in a custom ErrorHandler or pre-normalize tokens.","Update config when migrating IdPs — claim shapes rarely match one-to-one."],"exampleFix":"// before (token has \"role\": \"admin\", a string)\nconfig.JWTClaimPath = \"role[0]\"\n// after\nconfig.JWTClaimPath = \"role\"","handlingStrategy":"type-guard","validationCode":"func rolesIsArray(claims jwt.MapClaims, key string) bool {\n    _, ok := claims[key].([]any)\n    return ok\n}\n// startup check against a sample token:\n// if !rolesIsArray(sampleClaims, \"roles\") { log.Fatal(\"claim is not an array; use plain/dot path\") }","typeGuard":"func isArrayClaim(v any) bool {\n    _, ok := v.([]any)\n    return ok\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"claim value is not an array\") {\n    logger.Warn(\"claim shape mismatch; adjust JWTClaimPath (string vs array)\", \"err\", err)\n    http.Error(w, \"unauthorized\", http.StatusUnauthorized)\n    return\n}","preventionTips":["Check the claim's JSON type in a decoded token before choosing array notation","Make the IdP emit role claims as arrays consistently (multi-valued mapper)","Re-verify claim shapes after any IdP migration or client change"],"tags":["go","jwt","rbac","claims"],"backgroundTag":"jwt-claim-type-mismatch","analyzedSha":"187eb24962502e91f1fee856230670958b66e89c","analyzedAt":"2026-09-01T20:34:54.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}