Eric B
Eric B

Reputation: 4437

How to return 401 instead of 302 in ASP.NET Core?

I'm trying to get ASP.NET Core Identity to return 401 when a user isn't logged in. I've added an [Authorize] attribute to my method and instead of returning 401 it returns 302. I've tried a ton of suggestions but nothing seems to work, including services.Configure and app.UseCookieAuthentication setting LoginPath to null or PathString.Empty.

Upvotes: 43

Views: 25984

Answers (8)

Youp Bernoulli
Youp Bernoulli

Reputation: 5635

For ASP.NET Core 3.x (preview) using Identity with Cookie authentication this is what did the trick:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<IdentityContext>()
    .AddDefaultTokenProviders()
    .AddRoles<IdentityRole>();

services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.Headers["Location"] = context.RedirectUri;
        context.Response.StatusCode = 401;
        return Task.CompletedTask;
    };
});

This is what we see around everywhere in different variations. BUT, the essential point here is that ConfigureApplicationCookie must be specified AFTER AddIdentity. It's "sad" but true. This SO answer finally brought light in the darkness.

I have been scratching my head for over a day and tried many different variations:

  • Override the Authorize attribute (not so much to override in 3.x anymore)
  • Specifying options.Cookie.EventType with a Cookie (runtime error)
  • options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme (It was said that JWT bearer would not redirect to a login page)
  • And of course configuring the ApplicationCookie (but before the call to AddIdentity which doesn't work.

That all didn't work. But with the answer above I finally got the 401 Unauthorized returned (which should be Unauthenticated by the way)

Upvotes: 4

Anthony rono
Anthony rono

Reputation: 148

In continuation, I merged the previous answers into the following:

1. Startup.cs

services.ConfigureApplicationCookie(options =>
        {
            options.LoginPath = new PathString("/Account/Login");
            options.LogoutPath = new PathString("/Account/Logout");

            options.Events.OnRedirectToAccessDenied = context =>
            {
                if (wlt_AjaxHelpers.IsAjaxRequest(context.Request))
                {
                    context.Response.Clear();
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return Task.CompletedTask;
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
            };
        });

2. Helper custom class

public static class wlt_AjaxHelpers
     {

        public static bool IsAjaxRequest( HttpRequest request )
        {

            return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) ||
                string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal);
        }

    }

Upvotes: 2

Francis Ofosu Afriyie
Francis Ofosu Afriyie

Reputation: 151

For asp.net mvc core USE THIS INSTEAD

 services.ConfigureApplicationCookie(options =>
        {
            options.LoginPath = new PathString("/Account/Login");
            options.LogoutPath = new PathString("/Account/Logout");

            options.Events.OnRedirectToLogin = context =>
            {
                if (context.Request.Path.StartsWithSegments("/api")
                    && context.Response.StatusCode == StatusCodes.Status200OK)
                {
                    context.Response.Clear();
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return Task.CompletedTask;
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
            };
        });

Upvotes: 15

SirGordon
SirGordon

Reputation: 33

For me on ASP.NET Core 2.2.0 only this worked:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(
        options =>
        {
            options.LoginPath = new PathString("/Account/Login");
            options.LogoutPath = new PathString("/Account/Logout");

            options.Events.OnRedirectToLogin = context =>
            {
                if (context.Request.Path.StartsWithSegments("/api")
                    && context.Response.StatusCode == StatusCodes.Status200OK)
                {
                    context.Response.Clear();
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return Task.CompletedTask;
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
            };
        }
    );

Upvotes: 0

kroatti
kroatti

Reputation: 394

If the request header contains X-Requested-With: XMLHttpRequest the status code will be 401 instead of 302

private static bool IsAjaxRequest(HttpRequest request)
    {
        return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) ||
            string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal);
    }

See on gitHub: https://github.com/aspnet/Security/blob/5de25bb11cfb2bf60d05ea2be36e80d86b38d18b/src/Microsoft.AspNetCore.Authentication.Cookies/Events/CookieAuthenticationEvents.cs#L40-L52

Upvotes: 26

Matthew Steven Monkan
Matthew Steven Monkan

Reputation: 9110

As of ASP.NET Core 2.x:

services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.StatusCode = 401;    
        return Task.CompletedTask;
    };
});

Upvotes: 69

Mark Perry
Mark Perry

Reputation: 1725

services.Configure<IdentityOptions>(options =>
{
   options.Cookies.ApplicationCookie.LoginPath = new PathString("/");
   options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
   {
      OnRedirectToLogin = context =>
      {
         if (context.Request.Path.Value.StartsWith("/api"))
         {
            context.Response.Clear();
            context.Response.StatusCode = 401;
            return Task.FromResult(0);
         }
         context.Response.Redirect(context.RedirectUri);
         return Task.FromResult(0);
      }
   };
});

Source:

https://www.illucit.com/blog/2016/04/asp-net-5-identity-302-redirect-vs-401-unauthorized-for-api-ajax-requests/

Upvotes: 19

Eric B
Eric B

Reputation: 4437

Okay after digging around in the asp.net core unit tests I finally found a working solution. You have to add the following to your call to services.AddIdentity

services.AddIdentity<ApplicationUser, IdentityRole>(o => {
    o.Cookies.ApplicationCookie.AutomaticChallenge = false;
});

Upvotes: 5

Related Questions