Other 4xx codes
All HTTP status codes in ASP.NET Core
4xx Client error
The server understood who the client is but refuses the request: the user is authenticated but not allowed.
In ASP.NET Core: Sent when an authorization policy fails for an authenticated user.
The StatusCodes constant, HttpStatusCode enum name, reason phrase, class, and how EnsureSuccessStatusCode() and the standard resilience handler treat 403.
| Constant | StatusCodes.Status403Forbidden |
|---|---|
| HttpStatusCode | HttpStatusCode.Forbidden |
| Reason phrase | Kestrel sends Forbidden |
| Class | 4xx, client error |
| IsSuccessStatusCode | false |
| EnsureSuccessStatusCode() | throws HttpRequestException: Response status code does not indicate success: 403 (Forbidden). |
| Standard resilience handler | does not retry it |
Results.StatusCode(StatusCodes.Status403Forbidden)
Results.Problem(statusCode: StatusCodes.Status403Forbidden, detail: "...")StatusCode(StatusCodes.Status403Forbidden)
Problem(statusCode: StatusCodes.Status403Forbidden, detail: "...")What Results.Problem(statusCode: 403) sends (with AddProblemDetails()), as application/problem+json:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.4",
"title": "Forbidden",
"status": 403,
"traceId": "0HNA1B2C3D4E5:00000001"
}A controller's Problem(statusCode: 403) sends the same body.
A bare StatusCode(403) in an [ApiController] also gets a ProblemDetails body automatically; Results.StatusCode(403) in a minimal API sends an empty body unless you add UseStatusCodePages().
Setup
builder.Services.AddAuthorization(o => o.AddPolicy("admin", p => p.RequireRole("admin")));
app.MapGet("/admin", () => "ok").RequireAuthorization("admin");Request
GET /admin HTTP/1.1
Authorization: Bearer <valid token without a role claim>Response (recorded)
HTTP/1.1 403 ForbiddenThe token is valid, so the user is known, but the policy fails: 403 with an empty body. If a token has roles and you still get 403, check which claim type they arrive as.
Setup
app.MapGet("/api/admin", () => "ok").RequireAuthorization(p => p.RequireRole("admin"));Request
GET /api/admin HTTP/1.1
Cookie: <a signed-in user without the admin role>Response (recorded)
HTTP/1.1 302 Found
Location: http://localhost:5000/Account/AccessDenied?ReturnUrl=%2Fapi%2FadminThe cookie handler redirects forbidden requests to /Account/AccessDenied instead of answering 403.
Fix: Set options.Events.OnRedirectToAccessDenied to answer 403 for API paths.
Setup
app.MapGet("/r", () => Results.Forbid());
// no AddAuthentication()Request
GET /r HTTP/1.1Response (recorded)
HTTP/1.1 500 Internal Server ErrorForbid() does not write 403 itself: it asks the authentication handler to. With no authentication scheme registered it throws, and the client gets 500. ControllerBase.Forbid() behaves the same.
Fix: Register authentication, or return Results.StatusCode(StatusCodes.Status403Forbidden) directly.
Setup
public IActionResult F() => Forbid();
// no AddAuthentication()Request
GET /api/redirects/forbid HTTP/1.1Response (recorded)
HTTP/1.1 500 Internal Server ErrorSame as Results.Forbid(): with no authentication scheme it throws and the client gets 500.
Every response on this page was recorded from ASP.NET Core 10.0.12 on Kestrel in the Production environment; the HttpClient rows come from .NET 10.0.12 and Microsoft.Extensions.Http.Resilience 10.10.0.
All HTTP status codes in ASP.NET Core