{"record":{"id":"e894e9cd9c882c85","repo":"gofr-dev/gofr","slug":"failed-to-extract-role-from-jwt-w","errorCode":null,"errorMessage":"failed to extract role from JWT: %w","messagePattern":"failed to extract role from JWT: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gofr/rbac/middleware.go","lineNumber":239,"sourceCode":"\t}\n\n\t// No role found - no default role supported\n\treturn \"\", ErrRoleNotFound\n}\n\n// extractRoleFromJWT extracts the role from JWT claims in the request context.\n// It uses the JWTClaimPath from config to navigate the claim structure.\nfunc extractRoleFromJWT(r *http.Request, claimPath string) (string, error) {\n\t// Get JWT claims from context (set by OAuth middleware)\n\tclaims, ok := r.Context().Value(middleware.JWTClaim).(jwt.MapClaims)\n\tif !ok || claims == nil {\n\t\treturn \"\", fmt.Errorf(\"%w\", errJWTClaimsNotFound)\n\t}\n\n\t// Extract role using the configured claim path\n\trole, err := extractClaimValue(claims, claimPath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to extract role from JWT: %w\", err)\n\t}\n\n\t// Convert to string\n\troleStr, ok := role.(string)\n\tif !ok {\n\t\t// Try to convert if it's not a string\n\t\treturn fmt.Sprintf(\"%v\", role), nil\n\t}\n\n\treturn roleStr, nil\n}\n\n// extractClaimValue extracts a value from JWT claims using a dot-notation or array notation path.\n// Examples:\n//   - \"role\" -> claims[\"role\"]\n//   - \"roles[0]\" -> claims[\"roles\"].([]any)[0]\n//   - \"permissions.role\" -> claims[\"permissions\"].(map[string]any)[\"role\"]\nfunc extractClaimValue(claims jwt.MapClaims, path string) (any, error) {","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/rbac/middleware.go#L221-L257","documentation":"A wrapped error returned by extractRoleFromJWT when extractClaimValue fails to resolve JWTClaimPath inside the token's claims. The original sentinel (errClaimPathNotFound, errEmptyClaimPath, errInvalidArrayNotation, errClaimValueNotArray, etc.) is preserved via %w, prefixed with \"failed to extract role from JWT: \". Ultimately this bubbles up as ErrRoleNotFound from extractRole, producing a 401 by default.","triggerScenarios":"Any invalid JWTClaimPath against real tokens: empty path, key that doesn't exist (\"claim path not found: xyz\"), malformed array notation (\"roles[a]\"), non-array value with index notation, out-of-range index, or broken nested structure — each wrapped with this prefix inside extractRoleFromJWT.","commonSituations":"Deploying a service with a claim path copied from a different environment/IdP where claim names differ; renaming claims in the IdP without updating config; typos in JWTClaimPath committed to config; Keycloak realm_access nesting forgotten (path \"roles\" instead of \"realm_access.roles[0]\").","solutions":["Read the wrapped sentinel in the error text (after the prefix) to identify the exact cause, then fix Config.JWTClaimPath accordingly.","Decode a real production token (jwt.io) and mirror its exact claim structure in JWTClaimPath.","Add a startup smoke test that parses a sample token with your configured path to fail fast on mismatch.","If failures are per-user, ensure the IdP template includes the claim for all clients/users rather than changing the path."],"exampleFix":"// before (Keycloak token nests roles)\nconfig.JWTClaimPath = \"roles[0]\" // -> failed to extract role from JWT: claim key not found: roles\n// after\nconfig.JWTClaimPath = \"realm_access.roles[0]\"","handlingStrategy":"try-catch","validationCode":"// Validate JWTClaimPath against a sample token before deploy:\n// _, err := extractClaimValue(sampleClaims, cfg.JWTClaimPath)\n// if err != nil { log.Fatalf(\"JWTClaimPath invalid for sample token: %v\", err) }","typeGuard":"func pathResolves(claims jwt.MapClaims, path string) bool {\n    _, err := extractClaimValue(claims, path)\n    return err == nil\n}","tryCatchPattern":"role, err := extractRoleFromJWT(r, cfg.JWTClaimPath)\nif err != nil {\n    var cause error\n    // unwrap to the sentinel beneath the \"failed to extract role from JWT\" prefix\n    for e := err; e != nil; e = errors.Unwrap(e) { cause = e }\n    logger.Error(\"jwt role extraction failed\", \"cause\", cause)\n    http.Error(w, \"unauthorized\", http.StatusUnauthorized)\n    return\n}","preventionTips":["Smoke-test JWTClaimPath against a real token from each environment at startup","Read the wrapped sentinel after the prefix to pinpoint the failure mode","Mirror the exact claim structure from jwt.io when writing paths","Update claim paths in lockstep with IdP token-mapper changes"],"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"}