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
.

- 13,327
- 5
- 62
- 90

- 4,367
- 5
- 33
- 43
-
For anyone else following this same trail, I got here from https://devblog.dymel.pl/2016/07/07/return-401-unauthorized-from-asp-net-core-api/ – Brett Rossier Jan 21 '19 at 17:58
8 Answers
As of ASP.NET Core 2.x:
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});

- 8,170
- 4
- 53
- 71
-
3
-
4I believe kroatti's answer should be the accepted one. The above approach will cause issues if you want ajax requests to get 401 but non-ajax requests to get redirected. You can add logic to handle that, per Mark Perry's answer, but this logic is already built in to the framework for requests with X-Requested-With: XMLHttpRequest header. – Daniel May 01 '18 at 15:27
-
I wrote three different custom policies for my project. So, will this return 401 instead of 302 for all of my policies? – KaraKaplanKhan Jul 30 '18 at 08:00
-
4This worked. What an annoying feature in the framework. If we wanted a redirect, we would specify it... – kovac Feb 09 '19 at 16:41
-
-
Steven Pena is actually correct. You're not to 401 the login redirect, else your login page will not work. OnRedirectToAccessDenied is the correct answer in my opinion – Jeff Aug 18 '20 at 11:24
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);
}

- 374
- 2
- 4
-
2this should be the most pretty straightforward answer without hurting your code base. seesh, why i havent found you earlier. – Jeff Jun 28 '18 at 10:02
-
7This is not a very helpful answer, since it requires the caller to inject a proprietary header value. How about fixing the handler code to actually honor the Accept: header instead? My client code (angular) is sending "Accept: application/json" but getting back crap HTML as a response which it can't parse. The server side code IS the problem. – theta-fish Jan 22 '19 at 01:09
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:

- 1,705
- 10
- 12
-
-
Works great on .net core 2.1 - though I did have to change StartsWith("/api") to contains("api") but worked great after that. Guess I need to do something within my app now to detect 401's and redirect accordingly after any http request. Cheers! – DanAbdn Nov 04 '18 at 09:46
-
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;
};
});

- 151
- 1
- 3
-
-
3With ASP.NET Core 2.0 you can use `return Task.CompletedTask;` instead of `return Task.FromResult – SerjG Apr 10 '18 at 22:56
-
As mentioned elsewhere, make sure this is after the call to `services.AddIdentity(…)` or `AddDefaultIdentity(…)` – Jamie F Sep 12 '18 at 00:09
-
2Thanks, this works great. However, is there a way to return 401 and json (something like { authorize: "Failed" }) – howardlo Jan 25 '19 at 19:58
-
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;
});

- 4,367
- 5
- 33
- 43
-
Does this still re-direct the user to the login page? I'm getting ALOT of traffic to pages that, when clicked, will re-direct to the login page because of the 302. I still want that to work, but I want all of the bots to fail that call so they never call it again (a 401/403). A 302 will still tell them to keep calling it. (This is for the ones that don't honor the robots.txt file where I'm specifaclly "disallow"ing that url pattern) – ganders Dec 27 '16 at 15:40
-
1@ganders No. If you want your app to behave a certain way on 401, you'll have to handle it yourself. Typically a 401 should include a WWW-Authenticate header that describes how to authenticate. – Eric B Dec 27 '16 at 15:52
-
`WWW-Authenticate` header MUST be provided on 401. `If the protected resource request does not include authentication credentials or does not contain an access token that enables access to the protected resource, the resource server MUST include the HTTP "WWW-Authenticate" response header field` – urbanhusky May 17 '17 at 09:01
-
This doesn't seem to be present (anymore) in .NET Core 3.x (preview) :/ – Youp Bernoulli Aug 01 '19 at 10:48
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 toAddIdentity
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)

- 822
- 1
- 12
- 24

- 5,303
- 5
- 39
- 59
-
1There's no need to set the "location" header - that is only useful for a 3xx status code – Andy Feb 27 '20 at 11:14
-
Hi @Bernoulli, I am using asp.net core 3.1, have tried to put similar code in client application, but it did not work. – MayankGaur May 12 '20 at 05:00
-
What did not work? Did it not compile? Did it throw exceptions? What kind of exception / message? This answer's main point is the order in which "things" are declared which solved the OP's (and my) issue . – Youp Bernoulli May 12 '20 at 09:00
-
In my case it also didn't work. If I put a breakpoint in OnRedirectToLogin or OnRedirectToAccessDenied function, it never breaks. I can't figure out how to get this to work on .NET Core 3.1. My objective is to be able to redirect to login page or return status 401, based on whether the called method was an API method or web page. – killswitch Mar 29 '21 at 20:53
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);
}
}

- 68
- 5
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;
};
}
);

- 33
- 9