{"record":{"id":"eeff94d44d1dadde","repo":"gofr-dev/gofr","slug":"forbidden-access-denied-eeff94","errorCode":null,"errorMessage":"Forbidden: Access denied","messagePattern":"Forbidden: Access denied","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"pkg/gofr/rbac/middleware.go","lineNumber":196,"sourceCode":"\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\n\t\t// This ensures JWT is the only method when configured\n\t\treturn \"\", ErrRoleNotFound\n\t}\n","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/rbac/middleware.go#L178-L214","documentation":"The default error handler in handleAuthError writes \"Forbidden: Access denied\" with HTTP 403 for any authorization error that is NOT ErrRoleNotFound — most commonly ErrAccessDenied, returned when the role was extracted successfully but checkEndpointAuthorization denies it for the configured route. It is the terminal response when a valid, identified caller lacks permission for the endpoint.","triggerScenarios":"A request with a valid role hits an RBAC-configured endpoint whose allowed role list does not include that role (checkEndpointAuthorization returns false, handleAuthError receives ErrAccessDenied); also any non-ErrRoleNotFound error surfaces as 403 by default; if config.ErrorHandler is set, this branch never runs.","commonSituations":"User has role \"viewer\" but the endpoint only allows \"admin\"; role claim casing mismatch (\"Admin\" vs \"admin\") so no configured role matches; roles renamed in the IdP without updating RBAC route config; wildcard/case-sensitivity assumptions about role matching; stale role mappings after an org restructure.","solutions":["Compare the user's actual role (from the audit log's Role field) against the roles configured for that endpoint in the RBAC Config and add the missing role or fix casing.","Decode the token to confirm the claim value matches exactly (case-sensitive) the roles listed in config.","Update the identity provider's role assignments if the user genuinely needs access.","Supply a custom config.ErrorHandler if you need role-specific or localized 403 responses instead of the default plain text."],"exampleFix":"// before\nendpoints: {path: \"/admin\", method: \"GET\", allowedRoles: [\"admin\"]} // user is \"Admin\"\n// after\nendpoints: {path: \"/admin\", method: \"GET\", allowedRoles: [\"admin\", \"Admin\"]} // or normalize claim casing in IdP","handlingStrategy":"validation","validationCode":"func roleAllowed(role string, endpoint Endpoint) bool {\n    for _, r := range endpoint.AllowedRoles {\n        if r == role { return true }\n    }\n    return false\n}\n// pre-check role casing at startup against a sample token:\n// if !roleAllowed(sampleRole, ep) { log.Warn(\"sample role not in allowedRoles — check casing\") }","typeGuard":"func isAccessDenied(err error) bool { return errors.Is(err, ErrAccessDenied) }","tryCatchPattern":"authorized, _ := checkEndpointAuthorization(role, endpoint, cfg)\nif !authorized {\n    logger.Warn(\"rbac denied\", \"role\", role, \"route\", routeLabel, \"allowed\", endpoint.AllowedRoles)\n    http.Error(w, \"forbidden\", http.StatusForbidden)\n    return\n}","preventionTips":["Keep allowedRoles in config in the exact casing the IdP emits","Check the audit log's Role field when users report 403s","Synchronize role names between IdP and RBAC config in CI (shared constants or schema)","Re-review role mappings after org/tenant restructuring"],"tags":["go","rbac","http","forbidden"],"backgroundTag":"rbac-access-denied","analyzedSha":"187eb24962502e91f1fee856230670958b66e89c","analyzedAt":"2026-09-01T20:34:54.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}