AJAX and FormsAuthentication, how prevent FormsAuthentication overrides HTTP 401? AJAX and FormsAuthentication, how prevent FormsAuthentication overrides HTTP 401? asp.net asp.net

AJAX and FormsAuthentication, how prevent FormsAuthentication overrides HTTP 401?


I had the same problem, and had to use custom attribute in MVC. You can easy adapt this to work in web forms, you could override authorization of your pages in base page if all your pages inherit from some base page (global attribute in MVC allows the same thing - to override OnAuthorization method for all controllers/actions in application)

This is how attribute looks like:

public class AjaxAuthorizationAttribute : FilterAttribute, IAuthorizationFilter    {        public void OnAuthorization(AuthorizationContext filterContext)        {            if (filterContext.HttpContext.Request.IsAjaxRequest()                && !filterContext.HttpContext.User.Identity.IsAuthenticated                && (filterContext.ActionDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Count() > 0                || filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Count() > 0))            {                filterContext.HttpContext.SkipAuthorization = true;                filterContext.HttpContext.Response.Clear();                filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;                filterContext.Result = new HttpUnauthorizedResult("Unauthorized");                filterContext.Result.ExecuteResult(filterContext.Controller.ControllerContext);                filterContext.HttpContext.Response.End();            }        }    }

Note that you need to call HttpContext.Response.End(); or your request will be redirected to login (I lost some of my hair because of this).

On client side, I used jQuery ajaxError method:

var lastAjaxCall = { settings: null, jqXHR: null };var loginUrl = "yourloginurl";//...//...$(document).ready(function(){    $(document).ajaxError(function (event, jqxhr, settings) {            if (jqxhr.status == 401) {                if (loginUrl) {                    $("body").prepend("<div class='loginoverlay'><div class='full'></div><div class='iframe'><iframe id='login' src='" + loginUrl + "'></iframe></div></div>");                    $("div.loginoverlay").show();                    lastAjaxCall.jqXHR = jqxhr;                    lastAjaxCall.settings = settings;                }            }    }}

This showed login in iframe over current page (looking like user was redirected but you can make it different), and when login was success, this popup was closed, and original ajax request resent:

if (lastAjaxCall.settings) {        $.ajax(lastAjaxCall.settings);        lastAjaxCall.settings = null;    }

This allows your users to login when session expires without losing any of their work or data typed in last shown form.


I'm stealing this answer heavily from other posts, but an idea might be to implement an HttpModule to intercept the redirect to the login page (instructions at that link).

You could also modify that example HttpModule to only intercept the redirect if the request was made via AJAX if the default behavior is correct when the request is not made via AJAX:

Detect ajax call, ASP.net

So something along the lines of:

class AuthRedirectHandler : IHttpModule{    #region IHttpModule Members    public void Dispose()    {    }    public void Init(HttpApplication context)    {        context.EndRequest+= new EventHandler(context_EndRequest);    }    void context_EndRequest(object sender, EventArgs e)    {        HttpApplication app = (HttpApplication)sender;        if (app.Response.StatusCode == 302             && app.Request.Headers["X-Requested-With"] == "XMLHttpRequest"            && context.Response.RedirectLocation.ToUpper().Contains("LOGIN.ASPX"))        {            app.Response.ClearHeaders();            app.Response.ClearContent();            app.Response.StatusCode = 401;        }    }    #endregion}

You could also ensure the redirect is to your actual login page if there are other legit 302 redirects in your app.

Then you would just add to your web.config:

  <httpModules>    <add name="AuthRedirectHandler" type="SomeNameSpace.AuthRedirectHandler, SomeNameSpace" />  </httpModules>

Anyhow. Again, actual original thought went into this answer, I'm just pulling various bits together from SO and other parts of the web.


I was having issues implementing the accepted answer. Chiefly, my error logs were getting filled with Server cannot set status after HTTP headers have been sent errors.

I tried implementing the accepted answer to question Server cannot set status after HTTP headers have been sent IIS7.5, again no success.

Googling a bit I stumbled upon the SuppressFormsAuthenticationRedirect property

If your .Net version is >= 4.5, then you can add the following code to the HandleUnauthorizedRequest method of your custom AuthorizeAttribute class.

public sealed class CustomAuthorizeAttribute : AuthorizeAttribute{    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)    {        if (filterContext.HttpContext.Request.IsAjaxRequest())        {            filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;            base.HandleUnauthorizedRequest(filterContext);            return;        }        base.HandleUnauthorizedRequest(filterContext);        return;    }}

The important part is the if block. This is the simplest thing to do if you are on .Net 4.5 & already have custom authorization in place.