gofr-dev/gofr · error
failed to extract role from JWT: %w
Error message
failed to extract role from JWT: %w
What it means
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.
Source
Thrown at pkg/gofr/rbac/middleware.go:239
}
// No role found - no default role supported
return "", ErrRoleNotFound
}
// extractRoleFromJWT extracts the role from JWT claims in the request context.
// It uses the JWTClaimPath from config to navigate the claim structure.
func extractRoleFromJWT(r *http.Request, claimPath string) (string, error) {
// Get JWT claims from context (set by OAuth middleware)
claims, ok := r.Context().Value(middleware.JWTClaim).(jwt.MapClaims)
if !ok || claims == nil {
return "", fmt.Errorf("%w", errJWTClaimsNotFound)
}
// Extract role using the configured claim path
role, err := extractClaimValue(claims, claimPath)
if err != nil {
return "", fmt.Errorf("failed to extract role from JWT: %w", err)
}
// Convert to string
roleStr, ok := role.(string)
if !ok {
// Try to convert if it's not a string
return fmt.Sprintf("%v", role), nil
}
return roleStr, nil
}
// extractClaimValue extracts a value from JWT claims using a dot-notation or array notation path.
// Examples:
// - "role" -> claims["role"]
// - "roles[0]" -> claims["roles"].([]any)[0]
// - "permissions.role" -> claims["permissions"].(map[string]any)["role"]
func extractClaimValue(claims jwt.MapClaims, path string) (any, error) {View on GitHub (pinned to 187eb24962)
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.
Example fix
// before (Keycloak token nests roles) config.JWTClaimPath = "roles[0]" // -> failed to extract role from JWT: claim key not found: roles // after config.JWTClaimPath = "realm_access.roles[0]"
Defensive patterns
Strategy: try-catch
Validate before calling
// Validate JWTClaimPath against a sample token before deploy:
// _, err := extractClaimValue(sampleClaims, cfg.JWTClaimPath)
// if err != nil { log.Fatalf("JWTClaimPath invalid for sample token: %v", err) } Type guard
func pathResolves(claims jwt.MapClaims, path string) bool {
_, err := extractClaimValue(claims, path)
return err == nil
} Try / catch
role, err := extractRoleFromJWT(r, cfg.JWTClaimPath)
if err != nil {
var cause error
// unwrap to the sentinel beneath the "failed to extract role from JWT" prefix
for e := err; e != nil; e = errors.Unwrap(e) { cause = e }
logger.Error("jwt role extraction failed", "cause", cause)
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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]").
Related errors
- claim key not found
- claim value is not an array
- array index out of bounds
- invalid claim structure
- %w: %s
AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01).
Data as JSON: /api/errors/e894e9cd9c882c85.
Report an issue: GitHub.