ASP.NET Core  

Stop Redirects in ASP.NET Core APIs: Return Proper HTTP Status Codes for Authentication

Stop Redirects. Start Speaking HTTP Correctly.

APIs live and die by predictability.

One of the most common production issues I see in ASP.NET Core APIs is authentication behaving like a web app, not an API. The symptom is subtle, but the impact is huge:

❌ APIs returning 302 redirects and HTML pages instead of HTTP status codes.

Let’s fix that properly.

The Root Cause: Cookie Authentication Is UI-First

ASP.NET Core’s cookie authentication was designed primarily for browser-based applications (MVC, Razor Pages).

By default, when an unauthenticated user hits a protected endpoint:

  1. ASP.NET Core returns 302 Found

  2. Redirects the client to /Account/Login

  3. Sends back HTML

This is perfect for browsers.

But for APIs?

  • Mobile apps don’t follow redirects correctly

  • JavaScript clients receive HTML instead of JSON

  • API gateways misinterpret responses

  • Monitoring and observability suffer

What APIs Should Do Instead

APIs must follow HTTP semantics strictly.

Correct Behavior

ScenarioCorrect Status Code
Not authenticated401 Unauthorized
Authenticated but not allowed403 Forbidden
Token expired401 Unauthorized
Invalid credentials401 Unauthorized

No redirects.
No HTML.
Just status codes.

Default Behavior (Problematic)

Here’s what happens without configuration:

  
    GET /api/orders
302 Found
Location: /Account/Login
Content-Type: text/html
  

This breaks:

  • SPAs

  • Mobile apps

  • API consumers

  • Swagger UI in some cases

The Fix: API-Friendly Cookie Authentication

You can keep cookie authentication and make it API-correct by overriding redirect events.

Basic Fix: Return 401 Instead of Redirect

  
    builder.Services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
    };
});
  

What Changed?

BeforeAfter
302 Redirect401 Unauthorized
HTML Login PageEmpty / JSON-friendly response
Browser-centricAPI-centric

Handling Access Denied (403)

Authentication ≠ Authorization.

You should also override access denied behavior:

  
    builder.Services.ConfigureApplicationCookie(options =>
{
    options.Events = new CookieAuthenticationEvents
    {
        OnRedirectToLogin = ctx =>
        {
            ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return Task.CompletedTask;
        },
        OnRedirectToAccessDenied = ctx =>
        {
            ctx.Response.StatusCode = StatusCodes.Status403Forbidden;
            return Task.CompletedTask;
        }
    };
});
  

Now your API communicates intent clearly.

Adding JSON Error Responses (Recommended)

APIs should return machine-readable responses .

  
    options.Events.OnRedirectToLogin = ctx =>
{
    ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
    ctx.Response.ContentType = "application/json";

    return ctx.Response.WriteAsync("""
    {
        "error": "unauthorized",
        "message": "Authentication is required to access this resource."
    }
    """);
};
  

This improves:

  • Client error handling

  • Frontend UX

  • Debugging

Mixed Applications (Web + API)

If your app serves both MVC + APIs , apply logic selectively:

  
    options.Events.OnRedirectToLogin = ctx =>
{
    if (ctx.Request.Path.StartsWithSegments("/api"))
    {
        ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
    }

    ctx.Response.Redirect(ctx.RedirectUri);
    return Task.CompletedTask;
};
  

This keeps:

  • Browser UX intact

  • API behavior correct

Why This Matters in Real Systems

Problems This Fix Prevents

  • React apps receiving login HTML

  • Mobile apps crashing on auth failures

  • API gateways retrying redirects endlessly

  • Security tools misclassifying responses

Benefits

✔ Predictable API contracts
✔ Correct REST semantics
✔ Cleaner client code
✔ Easier observability
✔ Fewer production surprises

HTTP Status Codes: (API-Focused)

Below is a practical reference for API developers.

🔵 1xx – Informational

CodeMeaning
100Continue
101Switching Protocols
102Processing (WebDAV)
103Early Hints (NEW, HTTP/2+)

🟢 2xx – Success

CodeMeaning
200OK
201Created
202Accepted
204No Content
206Partial Content

🟡 3xx – Redirection (Avoid in APIs)

CodeMeaning
301Moved Permanently
302Found
303See Other
304Not Modified
307Temporary Redirect
308Permanent Redirect (NEWER)

🚨 APIs should rarely use these

🔴 4xx – Client Errors (Most Important for APIs)

CodeMeaning
400Bad Request
401Unauthorized
403Forbidden
404Not Found
405Method Not Allowed
409Conflict
410Gone
415Unsupported Media Type
422Unprocessable Entity
429Too Many Requests

⚫ 5xx – Server Errors

CodeMeaning
500Internal Server Error
501Not Implemented
502Bad Gateway
503Service Unavailable
504Gateway Timeout

Treat Auth as Part of Your API Contract

Authentication behavior is not an implementation detail .

It’s part of how clients interact with your system.

APIs should be boring, explicit, and predictable.

Returning a login page from an API violates that principle.

Summary

ASP.NET Core gives us excellent defaults—but defaults are optimized for web apps , not APIs .

If you expose APIs:

  • Override redirect behavior

  • Return correct status codes

  • Make failures explicit

  • Respect HTTP semantics

This small change prevents hours of debugging across teams.

Happy Coding!

I write about modern C#, .NET, and real-world development practices. Follow me on C# Corner for regular insights, tips, and deep dives.