36

How to disable standard ASP.NET handling of 401 response code (redirecting to login page) for AJAX/JSON requests?

For web-pages it's okay, but for AJAX I need to get right 401 error code instead of good looking 302/200 for login page.

Update: There are several solutions from Phil Haack, PM of ASP.NET MVC - http://haacked.com/archive/2011/10/04/prevent-forms-authentication-login-page-redirect-when-you-donrsquot-want.aspx

derigel
  • 3,218
  • 2
  • 19
  • 31

7 Answers7

24

In classic ASP.NET you get a 401 http response code when calling a WebMethod with Ajax. I hope they'll change it in future versions of ASP.NET MVC. Right now I'm using this hack:

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 302 && Context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
    {
        Context.Response.Clear();
        Context.Response.StatusCode = 401;
    }
}
Catalin DICU
  • 4,610
  • 5
  • 34
  • 47
  • This is a really good stop-gap solution if you need something fast that works. Otherwise I'd would try to implement @troethom answer. – Joseph Daigle Jan 07 '10 at 15:44
18

The ASP.NET runtime is developed so that it always will redirect the user if the HttpResponse.StatusCode is set to 401, but only if the <authentication /> section of the Web.config is found.

Removing the authentication section will require you to implement the redirection to the login page in your attribute, but this shouldn't be a big deal.

Troels Thomsen
  • 11,361
  • 4
  • 28
  • 29
  • The best way to implement your own redirection is to subclass AuthorizeAttribute, and when not authorized set the result to a RedirectResult instead of HttpUnauthorizedResult. – Joseph Daigle Jan 07 '10 at 15:44
  • 2
    Yes, but in this case the OP would like to send HTTP code 401, but not redirecting (to work properly with JSON). – Troels Thomsen Jan 08 '10 at 20:34
  • Works for me - although I configured the setting as instead of removing the attribute altogether. – Timothy Lee Russell Jun 15 '13 at 03:20
  • 3
    Um - except I am using Forms Authentication in the MVC controllers so actually this doesn't work for me. Need a 404 && Forms Authentication solution, not an either ||. – Timothy Lee Russell Jun 18 '13 at 04:18
  • 1
    whats the fix if you want to use forms authentication and have a custom 401 redirect? – DevDave Aug 29 '13 at 16:59
9

I wanted both Forms authentication and to return a 401 for Ajax requests that were not authenticated.

In the end, I created a custom AuthorizeAttribute and decorated the controller methods. (This is on .Net 4.5)

//web.config

<authentication mode="Forms">
</authentication>

//controller

[Authorize(Roles = "Administrator,User"), Response302to401]
[AcceptVerbs("Get")]
public async Task<JsonResult> GetDocuments()
{
    string requestUri = User.Identity.Name.ToLower() + "/document";
    RequestKeyHttpClient<IEnumerable<DocumentModel>, string> client =
        new RequestKeyHttpClient<IEnumerable<DocumentModel>, string>(requestUri);

    var documents = await client.GetManyAsync<IEnumerable<DocumentModel>>();

    return Json(documents, JsonRequestBehavior.AllowGet);
}

//authorizeAttribute

public class Response302to401 : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest())
            {
                filterContext.Result = new JsonResult
                {
                    Data = new { Message = "Your session has died a terrible and gruesome death" },
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet
                };
                filterContext.HttpContext.Response.StatusCode = 401;
                filterContext.HttpContext.Response.StatusDescription = "Humans and robots must authenticate";
                filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
            }
        }
        //base.HandleUnauthorizedRequest(filterContext);
    }
}
Timothy Lee Russell
  • 3,719
  • 1
  • 35
  • 43
  • what if I want to use standard forms authorize redirects when a user is not logged in, but also to invoke custom 401 redirects when a user does not have the relevant permission, any ideas? – DevDave Aug 30 '13 at 11:41
  • You will get normal forms redirects on methods that don't have this AuthorizeAttribute. You could also just expand this attribute by providing the "else" clause of if(!filterContext.IsAuthenticated) in which you would check permissions and then setup the redirect yourself. – Timothy Lee Russell Aug 30 '13 at 18:42
2

I don't see what we have to modify the authentication mode or the authentication tag like the current answer says.

Following the idea of @TimothyLeeRussell (thanks by the way), I created a customized Authorize attribute (the problem with the one of @TimothyLeeRussell is that an exception is throw because he tries to change the filterContext.Result an that generates a HttpException, and removing that part, besides the filterContext.HttpContext.Response.StatusCode = 401, the response code was always 200 OK). So I finally resolved the problem by ending the response after the changes.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class BetterAuthorize : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            //Set the response status code to 500
            filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            filterContext.HttpContext.Response.StatusDescription = "Humans and robots must authenticate";
            filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;

            filterContext.HttpContext.Response.End();
        }
        else
            base.HandleUnauthorizedRequest(filterContext);
    }
}
Sebastián Rojas
  • 2,776
  • 1
  • 25
  • 34
2

You can call this method inside your action,

 HttpContext.Response.End();

Example

public async Task<JsonResult> Return401()
{
    HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
    HttpContext.Response.End();
    return Json("Unauthorized", JsonRequestBehavior.AllowGet);
}

From MSDN: The End method causes the Web server to stop processing the script and return the current result. The remaining contents of the file are not processed.

2

You could also use the Global.asax to interrupt this process with something like this:

    protected void Application_PreSendRequestHeaders(object sender, EventArgs e) {
        if (Response.StatusCode == 401) {
            Response.Clear();
            Response.Redirect(Response.ApplyAppPathModifier("~/Login.aspx"));
            return;
        }
    }
Jared
  • 7,165
  • 6
  • 49
  • 52
  • 1
    I did this, except I checked for the 302 and X-Requested-With in the answer from @troethom above. – Marc Sep 09 '10 at 18:35
1

You could choose to create a custom FilterAttribute implementing the IAuthorizationFilter interface.

In this attribute you add logic to determine if the request are supposed to return JSON. If so, you can return an empty JSON result (or do whatever you like) given the user isn't signed in. For other responses you would just redirect the user as always.

Even better, you could just override the OnAuthorization of the AuthorizeAttribute class so you don't have to reinvent the wheel. Add the logic I mentioned above and intercept if the filterContext.Cancel is true (the filterContext.Result will be set to an instance of the HttpUnauthorizedResult class.

Read more about "Filters in ASP.NET MVC CodePlex Preview 4" on Phil Haacks blog. It also applies to the latest preview.

Troels Thomsen
  • 11,361
  • 4
  • 28
  • 29
  • But I still can't get 401 Response status code for AJAX request. Even if I set up response in Autorization filter. Since ASP.NET infrastructure that catch 401 response and redirecting to login page sits over ASP.NET MVC. Letting know that user is not logged by empty JSON result is not very right... – derigel Sep 24 '08 at 15:04
  • I didn't understand exactly what you had in mind until now. I am adding a new answer that will solve your problem. – Troels Thomsen Sep 24 '08 at 22:24