{"record":{"id":"17880a807a3103ba","repo":"gofr-dev/gofr","slug":"unauthorized-missing-or-invalid-role","errorCode":null,"errorMessage":"Unauthorized: Missing or invalid role","messagePattern":"Unauthorized: Missing or invalid role","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"pkg/gofr/rbac/middleware.go","lineNumber":192,"sourceCode":"\t\tspan.RecordError(safeErr)\n\t\tspan.SetStatus(codes.Error, safeErr.Error())\n\t}\n\n\t// Log audit event (always enabled when Logger is available)\n\t// Audit logging is automatically performed using GoFr's logger\n\tif config.Logger != nil {\n\t\tlogAuditEvent(config.Logger, r, role, route, false)\n\t}\n\n\t// Use custom error handler if provided\n\tif config.ErrorHandler != nil {\n\t\tconfig.ErrorHandler(w, r, role, route, err)\n\t\treturn\n\t}\n\n\t// Default error handling\n\tif errors.Is(err, ErrRoleNotFound) {\n\t\thttp.Error(w, \"Unauthorized: Missing or invalid role\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\thttp.Error(w, \"Forbidden: Access denied\", http.StatusForbidden)\n}\n\n// extractRole extracts the user's role from the request.\n// Supports header-based extraction (via RoleHeader) or JWT-based extraction (via JWTClaimPath).\n// Precedence: JWT takes precedence over header (JWT is more secure).\n// No default role is supported - role must be explicitly provided.\nfunc extractRole(r *http.Request, config *Config) (string, error) {\n\t// Try JWT-based extraction first (takes precedence - more secure)\n\tif config.JWTClaimPath != \"\" {\n\t\trole, err := extractRoleFromJWT(r, config.JWTClaimPath)\n\t\tif err == nil && role != \"\" {\n\t\t\treturn role, nil\n\t\t}\n\t\t// If JWT extraction fails but JWTClaimPath is set, don't fall back to header","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/rbac/middleware.go#L174-L210","documentation":"The default error handler in handleAuthError writes \"Unauthorized: Missing or invalid role\" with HTTP 401 when the authorization error matches ErrRoleNotFound via errors.Is. ErrRoleNotFound is returned by extractRole when neither JWT nor header extraction yields a role — i.e. the request arrived at a protected endpoint without an identifiable role. This is an authentication-adjacent problem: the middleware cannot even determine who/what role the caller has.","triggerScenarios":"JWTClaimPath is set but JWT extraction fails (claims missing from context or role claim absent) — extractRole then returns ErrRoleNotFound without falling back to the header; RoleHeader is set but the request lacks that header; neither JWTClaimPath nor RoleHeader configured and no default role exists; endpoint is RBAC-configured (non-public) but the caller sent no usable token.","commonSituations":"Client forgot to send the Authorization header or Bearer token; OAuth middleware not mounted before the RBAC middleware so JWT claims never reach the context; RoleHeader name mismatched (case or custom prefix like X- vs x- handled, but wrong name entirely); gateway strips the role header; tokens issued without the expected role claim.","solutions":["Ensure the OAuth/JWT middleware runs before rbac.Middleware so JWTClaim is populated in the request context.","Verify clients send the credential: Authorization: Bearer <token> or the configured RoleHeader on every request to protected routes.","Confirm the token actually contains the claim at JWTClaimPath (decode the token); note JWTClaimPath disables header fallback entirely.","Mount rbac.Middleware only on routes that require auth, and keep public endpoints in the Endpoints public list."],"exampleFix":"// before (OAuth middleware missing, RBAC sees no claims)\nr.Use(rbac.Middleware(cfg))\n// after\nr.Use(middleware.JWTValidation(\"secret\"))\nr.Use(rbac.Middleware(cfg))","handlingStrategy":"try-catch","validationCode":"// Ensure middleware order and credentials before calling protected routes:\n// server.Use(middleware.OAuth(...)); server.Use(rbac.Middleware(cfg))\nfunc requestHasAuth(r *http.Request) bool {\n    return r.Header.Get(\"Authorization\") != \"\"\n}","typeGuard":"func hasJWTClaims(r *http.Request) bool {\n    claims, ok := r.Context().Value(middleware.JWTClaim).(jwt.MapClaims)\n    return ok && claims != nil\n}","tryCatchPattern":"role, err := extractRole(r, cfg)\nif err != nil {\n    if errors.Is(err, ErrRoleNotFound) {\n        logger.Warn(\"no role: check token presence and middleware order\", \"path\", r.URL.Path)\n    }\n    http.Error(w, \"unauthorized\", http.StatusUnauthorized)\n    return\n}","preventionTips":["Mount the OAuth/JWT middleware before rbac.Middleware in every service","Verify clients always send Authorization: Bearer <token> to RBAC-protected routes","Remember: setting JWTClaimPath disables header fallback entirely","Add integration tests hitting protected routes with and without tokens"],"tags":["go","rbac","http","unauthorized"],"backgroundTag":"missing-role-claim","analyzedSha":"187eb24962502e91f1fee856230670958b66e89c","analyzedAt":"2026-09-01T20:34:54.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}