Prevent Forms Authentication Login Page Redirect When You Don’t Want It, mvc, code 0 comments suggest edit

redirect Go that way instead - Photo by JacobEnos CC some rights reserved

Update: It looks like ASP.NET 4.5 adds the ability to suppress forms authentication redirect now with the HttpResponse.SuppressFormsAuthenticationRedirect property.

In an ASP.NET web application, it’s very common to write some jQuery code that makes an HTTP request to some URL (a lightweight service) in order to retrieve some data. That URL might be handled by an ASP.NET MVC controller action, a Web API operation, or even an ASP.NET Web Page or Web Form. If it can return curly brackets, it can be respond to a JavaScript request for JSON.

One pain point when hosting lightweight HTTP services on ASP.NET is making a request to a URL that requires authentication. Let’s look at a snippet of jQuery to illustrate what I mean. The following code makes a request to /admin/secret/data. Let’s assume that URL points to an ASP.NET MVC action with the AuthorizeAttribute applied, which requires that the request must be authenticated.

    url: '/admin/secret/data',
    type: 'POST',
    contentType: 'application/json; charset=utf-8',
    statusCode: {
        200: function (data) {
            alert('200: Authenticated');
            // Bind the JSON data to the UI
        401: function (data) {
            alert('401: Unauthenticated');
            // Handle the 401 error here.

If the user is not logged in when this code executes, you would expect that the 401 status code function would get called. But if forms authentication (often called FormsAuth for short) is configured, that isn’t what actually happens. Instead, you get a 200  with the contents of the login page (or a 404 if you don’t have a login page). What gives?

If you crack open Fiddler, it’s easy to see the problem. Instead of the request returning an HTTP 401 Unauthorized status code, it instead returns a 302 pointing to a login page. This causes jQuery (well actually, the XmlHttpRequest object) to automatically follow the redirect and issue another request to the login page. The login page handles this new request and return its contents with a 200 status code. This is not the desired result as the code expects JSON data to be returned in response to a 200 code, not HTML for the login page.

This “helpful” behavior when requesting a URL that requires authentication is a consequence of having the FormsAuthenticationModule enabled, which is the default in most ASP.NET applications. Under the hood, the FormsAuthenticationModule hooks into the request pipeline and changes any request that returns a 401 status code into a redirect to the login page.

Possible Solutions

I’m going to cover a few possible solutions I’ve seen around the web and then present the one that I prefer. It’s not that these other solutions are wrong, but they are only correct in some cases.

Remove Forms Authentication

If you don’t need FormsAuth, one simple solution is to remove the forms authentication module as this post suggests. This is a great solution if you’re sole purpose is to use ASP.NET to host a Web API service and you don’t need forms authentication. But it’s not a great solution if your app is both a web application and a web service.

Register an HttpModule to convert Redirects to 401

This blog post suggests registering an HTTP Module that converts any 302 request to a

  1. There are two problems with this approach. The first is that it breaks the case where the redirect is legitimate and not the result of FormsAuth. The second is that it requires manual configuration of an HttpModule.

Install-Package MembershipService.Mvc

My colleague, Steve Sanderson, has an even better approach with his MembershipService.Mvc and MembershipService.WebForms NuGet packages. These packages expose ASP.NET Membership as a service that you can call from multiple devices.

For example, if you want your Windows Phone application to use an ASP.NET website’s membership system to authenticate users of the application, you’d use his package. He provides the MembershipClient.WP7 and MembershipClient.JavaScript packages for writing clients that call into these services.

These packages deserve a blog post in their own right, but I’m going to just focus on the DoNotRedirectLoginModule he wrote. His module takes a similar approach to the previous one I mentioned, but he checks for a special value in HttpContext.Items, a dictionary for storing data related to the current request, before reverting a redirect back to a 401.

To prevent a FormsAuth redirect, an action method (or ASP.NET page or Web API operation) would simply call the helpful method DoNotRedirectToLoginModule.ApplyForRequest. This sets the special token in HttpContext.Items and the module will rewrite a 302 that’s redirecting to the login page back to a 401.

My Solution

Steve’s solution is a very good one. But I’m particularly lazy and didn’t want to have to call that method on every action when I’m writing an Ajax heavy application. So what I did was write a module that hooks in two events of the request.

The first event, PostReleaseRequestState, occurs after authentication, but before the FormsAuthenticationModule converts the status to a 302. In the event handler for this event, I check to see if the request is an Ajax request by checking that the X-Requested-With request header is “XMLHttpRequest”.

If so, I store away a token in the HttpContext.Items like Steve does. Then in the EndRequest event handler, I check for that token, just like Steve does. Inspired by Steve’s approach, I added a method to allow explicitly opting into this behavior, SuppressAuthenticationRedirect.

Here’s the code for this module. Warning: Consider this “proof-of-concept” code. I haven’t tested this thoroughly in a wide range of environments.

public class SuppressFormsAuthenticationRedirectModule : IHttpModule {
  private static readonly object SuppressAuthenticationKey = new Object();

  public static void SuppressAuthenticationRedirect(HttpContext context) {
    context.Items[SuppressAuthenticationKey] = true;

  public static void SuppressAuthenticationRedirect(HttpContextBase context) {
    context.Items[SuppressAuthenticationKey] = true;

  public void Init(HttpApplication context) {
    context.PostReleaseRequestState += OnPostReleaseRequestState;
    context.EndRequest += OnEndRequest;

  private void OnPostReleaseRequestState(object source, EventArgs args) {
    var context = (HttpApplication)source;
    var response = context.Response;
    var request = context.Request;

    if (response.StatusCode == 401 && request.Headers["X-Requested-With"] == 
      "XMLHttpRequest") {

  private void OnEndRequest(object source, EventArgs args) {
    var context = (HttpApplication)source;
    var response = context.Response;

    if (context.Context.Items.Contains(SuppressAuthenticationKey)) {
      response.TrySkipIisCustomErrors = true;
      response.StatusCode = 401;
      response.RedirectLocation = null;

  public void Dispose() {

  public static void Register() {

There’s a package for that

Warning: The following is proof-of-concept code I’ve written. I haven’t tested it thoroughly in a production environment and I don’t provide any warranties or promises that it works and won’t kill your favorite pet. You’ve been warmed.

Naturally, I’ve written a NuGet package for this. Simply install the package and all Ajax requests that set that header (if you’re using jQuery, you’re all set) will not be redirected in the case of a 401.

Install-Package AspNetHaack

Note that the package adds a source code file in App_Start that wires up the http module that suppresses redirect. If you want to turn off this behavior temporarily, you can comment out that file and you’ll be back to the old behavior.

The source code for this is in Github as part of my broader CodeHaacks project.

Why don’t you just fix the FormsAuthenticationModule?

We realize this is a deficiency with the forms authentication module and we’re looking into hopefully fixing this for the next version of the Framework.

Update: As I stated at the beginning, a new property added in ASP.NET 4.5 supports doing this.

Tags:, aspnetmvc, formsauth, membership

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.



43 responses

  1. Avatar for nacho10f
    nacho10f October 4th, 2011

    Steve Sanderson's packages really do deserve their own blog post.. I have no idea how to use them :s.

  2. Avatar for Fujiy
    Fujiy October 4th, 2011

    Recently I needed something similar. When user don´t have permission, returns a 401, and show a custom Html(custom error don´t work for 401 errors)
    Additionally I needed to personalize assigned Roles for Anonymous users too. I wrote a custom AuthorizeAttribute that do this 3 things
    ASP.NET vNext could have this 3 features by default....
    -Authorize attribute(or web.config declarative way for webforms) return a 401 error
    -Custom errors that works with 401 errors(maybe when fixing the first bug, this will automatically fixed)
    -Roles for anonymous users

  3. Avatar for Matt Honeycutt
    Matt Honeycutt October 4th, 2011

    Why not utilize the IsAjaxRequest extension method instead of checking headers directly? They are equivalent, but one is much more readable than the other...

  4. Avatar for Michel Grootjans
    Michel Grootjans October 4th, 2011

    Wouldn't it make more sense to have this be handled by a different HttpModule based on the url's extension.
    In Rails, if the URL has no extension, it's supposed to be a browser call, where you might respond with a redirect.
    If the URL has a .json extension, you get a plain error, not a redirect.

  5. Avatar for Saeed Neamati
    Saeed Neamati October 4th, 2011

    We had the same problem. But what we did, was to hook to AuthenticateRequest (just like you did) and we also checked the request to see if it's ajax or not (again, just like what you did). But at this point, we simply returned a JSON like {location: ''} and we simply ended response in that method with HTTP code 200. This way, jQuery still gets a JSON result. But if the result has a "location" property, we simply do a client-side redirect to login page. That's our way and it works like a charm.

  6. Avatar for Mike
    Mike October 4th, 2011

    Thanks for the work-around. Can you ask the ASP.NET team that owns this module to add a configuration attribute for web.config, because I think that would be much easier.

  7. Avatar for Goran Obradovic
    Goran Obradovic October 4th, 2011

    Nice package. Would come handy as I would have used it if it existed about 2 moths ago, but I will use it next time :)
    Anyway, I solved it without module then, but as I didn't wrote about it, here is how if there is anyone like this guy who cannot/wont use modules:

  8. Avatar for Andy McGoldrick
    Andy McGoldrick October 4th, 2011

    Great post.
    Do you need to create a HTTP module for this?
    As your MVC application implements System.Web.HttpApplication you could wire all this up on the application_start and add the event handlers for the events required in the application class.
    This might not be as tidy or reusable but for a single web app its probably less scary for folk who want to avoid HTTP Handlers.

  9. Avatar for Joseph Daigle
    Joseph Daigle October 4th, 2011

    If you want a super-simple 5 line solution, just turn all AJAX requested 302 responses into 401s:
    At the end of the day, though, we ended up writing our own forms authentication module.

  10. Avatar for dotnetchris
    dotnetchris October 4th, 2011

    What was Microsoft thinking when they needed to create "TrySkipIisCustomErrors"? That is one of the most nonsense ideas ever. Oh you said return a 410 error, you couldn't have meant to do that, we're going to do something else for you. Wait you said for us to to try to skip our custom "helping" carry on then.

  11. Avatar for Michael
    Michael October 4th, 2011

    Great article.

    P.S. The font used for comments when displayed in Chrome is super small. I have to zoom in to read them.

  12. Avatar for Nicholas Carey
    Nicholas Carey October 5th, 2011

    Thanks for trying to fix this problem. Forms Authentication turning returning 302 instead of a 401 and turning any 401 into a 302 redirect has always been most vexing been a headache for years.

    Why don’t you just fix the FormsAuthenticationModule?
    We realize this is a deficiency with the forms authentication module and we’re looking into hopefully fixing this for the next version of the Framework.

    Specification of the correct behaviour has been part of the HTTP standard for more than 15 years now. HTTP 1.1, RFC 2616 (and before that, HTTP 1.0, RFC 1945) specify the correct behavior in sections 10.4.2 and 9.4 respectively (the verbiage hasn't changed significantly):

    10.4.2 401 Unauthorized The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource.
    The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials.
    If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information. HTTP access authentication is explained in "HTTP Authentication: Basic and Digest Access Authentication" [43].
  13. Avatar for Srini
    Srini October 7th, 2011

    Great Post!

  14. Avatar for Jarrett Vance
    Jarrett Vance October 8th, 2011

    The last part of this post is hilarious:
    Why don’t you just fix the FormsAuthenticationModule?
    I look forward to this being fixed in 4.5... please!
    Here is my ugly solution in Application_Error:

    // read error page from config
    string redirectUrl = null;
    if (this.Context.IsCustomErrorEnabled)
    var section = WebConfigurationManager.GetSection("system.web/customErrors") as CustomErrorsSection;
    if (section != null) {
    redirectUrl = section.DefaultRedirect;
    if (section.Errors.Count > 0) {
    CustomError item = section.Errors[statusCode.ToString()];
    if (item != null) redirectUrl = item.Redirect;
    // load error page
    this.Context.Response.StatusCode = statusCode;
    this.Context.Response.TrySkipIisCustomErrors = true;
    // ** do not redirect when ajax **
    if ((this.Context.Request["X-Requested-With"] == "XMLHttpRequest") ||
    ((this.Context.Request.Headers != null) && (this.Context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")))
    // only redirect if there is a url
    if (!string.IsNullOrEmpty(redirectUrl))

  15. Avatar for Anas Ghanem
    Anas Ghanem October 8th, 2011

    What about calling the HttpContext.Current.SkipAuthorization ?

  16. Avatar for haacked
    haacked October 10th, 2011

    @Anas, that skips authorization. If you didn't want authorization to happen on that method, just remove the AuthorizeAttribute. The point is that you really *do* want authorization.

  17. Avatar for Konstantin
    Konstantin October 12th, 2011

    This solution didn't work for me, since UrlAuthorizationModule just ended the request, so PostReleaseRequestState event was not raised.

  18. Avatar for Softlion
    Softlion October 22nd, 2011

    I wrote an AjaxAwareAuthorizeAttribute, inherited from AuthorizeAttribute, which returns an http 409 response (Switch Proxy) instead of a 401.
    It sets the X-Redirect header to the intended redirect url and jQuery handles this globally as demonstrated in this blog post.
    Ok you need to know which action is called by ajax, and set this filter specifically on those. My first approach was less intrusive as you leave the [Authorize] attribute but does not work as explained below.
    My first idea : intercept action responses by setting once a global MVC action filter, which detects the result type and replace it with an AjaxRedirectResonse which returns 409 as above.
    But MVC has a special handling of authorize filters : after MVC calls an authorize filter, if this filter changes the action result then MVC will bypasse all other filters, whichever type they have.
    And [Authorize] is an authorize filter ... So this approach does not work and i sticked with the above version.

    Bonus 1:
    I wrote an AjaxAwareRedirectResult result type, which will
    X-Redirect but also X-Redirect-Top. This handles the case where you want to redirect outside an iframe.
    Bonus 2:
    In AjaxAwareAuthorizeAttribute I modified the way FormsAuthentication gets the redirect url because the web.config version is not aware of areas and i needed one different auth page per area. But this is another story.

  19. Avatar for Jarrett Vance
    Jarrett Vance December 8th, 2011

    Just realized I wrote a module using same method 3 years ago. See code here

  20. Avatar for Sheron Benedict
    Sheron Benedict January 9th, 2012

    This does not seem to work with WCF REST. When I debugged the HTTP module, I could see that the PostReleaseRequestState event is not being triggered at all. So when the EndRequest event handler executes, it does not find the token in the context. And hence the HTTP status code in the response remains as a 302 redirect.
    In the end, I decided on a approach where I check in EndRequest if the redirect is to the login page. If so, I override it and return 401. Like this:

    if (context.Response.IsRequestBeingRedirected && context.Response.RedirectLocation.Contains(FormsAuthentication.LoginUrl))
    response.TrySkipIisCustomErrors = true;
    response.StatusCode = (int)HttpStatusCode.Unauthorized;
    response.RedirectLocation = null;

  21. Avatar for Nirvan B
    Nirvan B January 18th, 2012

    I had successfully implemented the above IHttpModule in my web application and tested it on IIS 7.5 which I use for staging on my computer. It all worked well and so I deployed my web application on live hosting server. But, somehow on live server, the unauthenticated ajax-requests, forced browser to trigger windows/basic/digest authentication on the browser. Since I was using only Forms authentication in my web app, I was confused as to why the browser presented such "authentication required" popups, before running the ajax error handler (which redirects to forms login page). Later, I noticed that IIS adds "WWW-Authenticate" headers for a 401 request, so such 401 responses trigger the "Authentication Required" popup in browser before even running the ajax error handler for the ajax request. The only solution seems to me is to disable the Basic, Digest and Windows Authentication at web application level in IIS. This problem did not occurred on my staging server as Basic, Digest and Windows authentication modes were disabled for the web application.
    Any better suggestions to tackle the problem that I am facing ?

  22. Avatar for Martin Odhelius
    Martin Odhelius January 19th, 2012

    Your first request shall probably result in a 401 that redirects to the login page, but then if the user is not able to authorize himself you shall probably consider to through HTTP error code 403.
    "403 Forbidden
    The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead. "
    Best Regards

  23. Avatar for Adrian
    Adrian January 21st, 2012

    You can also use MADAM (, which is similar to the module you describe. Check my answer on SO for more details.

  24. Avatar for Chin Bae
    Chin Bae January 28th, 2012

    I found that that PostReleaseRequestState event isn't firing in my WCF Web Api application either. I was thinking, why bother doing all this opt-in nonsense with the HttpContext.Items dictionary?
    Can't you just check if the Response.StatusCode == 302 and Request.Headers["X-Requested-With"] is anything other than null and then change the StatusCode accordingly?
    Are there any scenarios in which you'd want a 302 to go through other than when Request.Headers["X-Requested-With"] is null (i.e. through a regular browser request)?
    The only question would be how reliable is Request.Headers["X-Requested-With"] as a means of determining the source of the request?
    Are there any scenarios in which a regular browser request wouldn't have Request.Headers["X-Requested-With"] == null?
    Are there any scenarios in which a non-browser requests would also cause Request.Headers["X-Requested-With"] == null?
    For example, what's the Request.Headers["X-Requested-With"] value when making a request with the WebClient class or the HttpWebRequest class?

  25. Avatar for Preetham Reddy
    Preetham Reddy March 24th, 2012

    Does it work on Azure? What happens if the request gets redirected to another server? Doesn't it make more sense to store that info in an External (Out of Proc) Session State?

  26. Avatar for acak7
    acak7 April 4th, 2012

    Thanks. Here's what I found. You can induce a 401, while retaining FormsAuthentication, by creating an /Account/Login path that can never be authorized.
    public class AccountController : Controller
    [Authorize(Roles = "NonExistentRole")]
    public ActionResult Login(string returnUrl)
    return View();
    Would there be any issues with this (apart from having a role called "NonExistentRole") ?

  27. Avatar for Shrike
    Shrike April 20th, 2012

    I'm trying to use the SuppressFormsAuthenticationRedirectModule in my ASP.NET MVC&WebAPI application.
    The module is being called (I can debug it). But after I leave its OnEndRequest I get an exception on the client:
    HttpException: Cannot redirect after HTTP headers have been sent.
    System.Web.HttpResponse.Redirect(String url, Boolean endResponse, Boolean permanent)
    System.Web.Security.FormsAuthenticationModule.OnLeave(Object source, EventArgs eventArgs)
    System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
    So it seems that FormsAuthenticationModule manages to enject after your SuppressFormsAuthenticationRedirectModule.OnEndRequest.
    The same error happens when there's no SuppressFormsAuthenticationRedirectModule at all. So it just can't change things.
    So I still need a solution for disabling FormsAuthModule' redirection.

  28. Avatar for Yannick
    Yannick October 22nd, 2012

    How do I hook this into a web-api request. For some reason the request doesn't fire the code.

  29. Avatar for Jake
    Jake January 10th, 2013

    Still not fixed in .NET 4.5, seems like this will never be fixed :(

  30. Avatar for Ashwin Seshadri
    Ashwin Seshadri January 10th, 2013

    Hi, I was trying to extend this logic to apply to requests for unauthorized static content (Say, some static content protected by denying unauthentication users access to the location of a static resource in web.config). But I see that the request is not available to my http module beyond the PostAuthenticate application event in the pipeline. In fiddler it somehow shows up with a 302 status. My configuration is as follows: runAllManagedModulesForAllRequests="true" and there is no proecondition of managed handler for my module. Any help to find a solution to my problem is greatly appreciated.


  31. Avatar for dotnetchris
    dotnetchris February 19th, 2013

     The fear of breaking changes even from code that was written half a decade ago is insurmountable it seems.

  32. Avatar for Wayne Brantley
    Wayne Brantley February 27th, 2013

    I found a way to do it in .net 4.5 that is built in.  Additionally a hack way in 3.5.   What you guys are doing above destroys the response and that is not good - along with 401 there can and is often times a payload.  See code here.

  33. Avatar for dotnetchris
    dotnetchris February 28th, 2013

     Fantastic find.

  34. Avatar for rarous
    rarous August 7th, 2013

    IMO the sign-in page should behave correctly according to HTTP protocol. So there is easy fix If ther is redirect to SignIn action there is also ReturnUrl parameter specified.

  35. Avatar for Mario
    Mario September 15th, 2013

    Thank you. Used this solution and it worked. My 401 returns don't get redirected. Did not use the entire package. Just the source code listed here. Registration of the module was done by including:

    <add name="SuppressFormsAuthenticationRedirectModule" type="AssemblyNameAndWorkspace.SuppressFormsAuthenticationRedirectModule"/>

    in the Web.config. Therefore the Register method is not required in this situation.

  36. Avatar for Political Football
    Political Football September 25th, 2013

    I assume that this will also work Windows Identity Foundation authorization?

  37. Avatar for Shince Jose
    Shince Jose November 19th, 2013

    You can use this approach of FormsAuthenticationTicket also for .Net<4.5. this works with ajax calls.

    FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, UserId.ToString(),
    DateTime.Now, DateTime.Now.AddMinutes(30), Persist, "");
    string cookiestr = FormsAuthentication.Encrypt(fat);
    HttpCookie ck = new HttpCookie(FormsAuthentication.FormsCookieName, cookiestr);
    if (Persist)
    ck.Expires = fat.Expiration;
    ck.Path = FormsAuthentication.FormsCookiePath;

  38. Avatar for Frederik Nielsen
    Frederik Nielsen February 19th, 2014

    Thanks for this great post. It now works excactly like i want.

    Added this to global.asax:
    protected void Application_BeginRequest(object sender, EventArgs e)
    HttpApplication context = (HttpApplication)sender;
    context.Response.SuppressFormsAuthenticationRedirect = true;

  39. Avatar for Carlos Konstanski
    Carlos Konstanski November 27th, 2014

    To force Basic Auth, I had to make this modification:

    private void OnEndRequest(object sender, EventArgs args) {
    HttpApplication context = (HttpApplication)sender;
    HttpResponse response = context.Response;
    if (context.Context.Items.Contains(SuppressAuthenticationKey)) {
    response.TrySkipIisCustomErrors = true;
    response.StatusCode = 401;
    response.RedirectLocation = null;
    response.AddHeader("WWW-Authenticate", "Basic realm=\"My Realm\"");

  40. Avatar for Pedro Llanes
    Pedro Llanes November 13th, 2015

    Hello, I have a problem. I has an application mvc with angularjs for front-end. I have a funtion to send an email to some users with a link (angular url). When I click on it, mvc redirect to login (that's right) but returnUrl omit angular part of url ("#angularurl"). can you help me? Thanks!!

  41. Avatar for david d
    david d November 29th, 2015

    If you reside in the alternate reality called, you will need to do a little more work. The App_Start code in C#

    Create a VB module under App_Start with the following code:

    Public Module FormsAuthConfig

    Public Sub Register()


    End Sub

    End Module

    And add the following code to global.asax under the Application_Start method:


    You are now in business. Also need to send a 401 response from your auth filter you need the following code:

    filterContext.Result = New HttpUnauthorizedResult

    Have fun.

  42. Avatar for Phil Beresford
    Phil Beresford February 11th, 2016

    I know this blog entry is a thousand years old now, but just the fact that you mentioned HTTP modules as a way of doing useful stuff got me out of a two day pit of abstraction mayhem.

    When it comes down to it, we're just processing bits on the wire...something that HTTP Modules are pretty good at, so thank you!

  43. Avatar for haacked
    haacked February 11th, 2016

    Glad it helped!