0

When my application encounters an exception of type UnauthorizedAccessException during an AJAX request, I want to handle the behaviour myself and return a custom JSON response.

So I have overridden the OnException method in a base controller, which all my conrtollers inherit from, like this:

protected override void OnException(ExceptionContext filterContext)
{
    var exception = filterContext.Exception;

    if (exception is UnauthorizedAccessException)
    {
        filterContext.ExceptionHandled = true;

        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
            filterContext.HttpContext.Response.ContentType = "application/json";

            JavaScriptSerializer serializer = new JavaScriptSerializer();
            string json = serializer.Serialize(new { IsUnauthenticated = true });
            filterContext.HttpContext.Response.Write(json);

            filterContext.HttpContext.Response.End();
        }
        else
        {
            filterContext.Result = RedirectToAction("LogOut", "Account");
        }
    }
    else
    {
        // Allow the exception to be processed as normal.
        base.OnException(filterContext);
    }
}

Now this pretty much does exactly what I want. If the exception occurs during an AJAX request, my JavaScript will get the correct JSON object back as desired.

However, the problem is that the application then suffers a HttpException internally, with message:

Cannot redirect after HTTP headers have been sent.

And the stack trace from the exception:

at System.Web.HttpResponse.Redirect(String url, Boolean endResponse, Boolean permanent) at System.Web.Security.FormsAuthenticationModule.OnLeave(Object source, EventArgs eventArgs) at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

I get this exception information when adding a breakpoint to the Application_Error method of MvcApplication like this:

protected void Application_Error(object sender, EventArgs e)
{
    Exception ex = Server.GetLastError();
    LogError(ex);
}

So although my application is producing the result exactly as I want in terms of user experience. I have this "behind the scenes" exception that I really don't want to happen.

What is going wrong here? And what can I do to prevent the exception from occurring?

6
  • Are you sure that Application_Error is an issue? If you comment it, does an error disapear? Commented Jun 29, 2018 at 14:19
  • 1
    oh, i misunderstand you, Sorry! Your Application_Error is fine. Commented Jun 29, 2018 at 14:28
  • Can you please clarify, you get this HttpException when your controller throws UnauthorizedAccessException AND filterContext.HttpContext.Request.IsAjaxRequest() is false? Commented Jun 29, 2018 at 14:33
  • Sorry musefan, I can't replicate for both Ajax requests and on the redirect. I always get the desired result. Your error literally means what it says, so you must be sending a response elsewhere in your code too or you're redirecting after your response from the error handler. Commented Jun 29, 2018 at 14:36
  • @vasily.sib: It happens after the code where ISAjaxRequest is true. However, I have no idea what is throwing it. The call stack in VS is empty at the point of exception. I will add exception stack trace to the question though.... Commented Jun 29, 2018 at 14:38

2 Answers 2

1

Continuing from the comments, I figured it's best to post it as an answer due to character limit. This is partly an answer as I don't have a right project in-front of me to test.

When I said in the comment that I can't replicate, that was because I ran those tests in the project I'm working on which is WebApi2 and I won't have the same behavior.

If my mind serves me right, your problem lies in the fact that you're implementing API like functionality into MVC project and of course what you're seeing is the expected behavior.

When you get UnauthorizedException the framework will try to redirect you to the login screen automatically (or an error page). You need to disable that behavior (obviously).

What you can try is supressing it in the handler using the below:

filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
filterContext.HttpContext.Response.Redirect(null);

End result should be something along the lines of:

if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
    filterContext.HttpContext.Response.StatusCode = 
    (int)System.Net.HttpStatusCode.Unauthorized;

    filterContext.HttpContext.Response.ContentType = "application/json";



    filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;

    JavaScriptSerializer serializer = new JavaScriptSerializer();
    string json = serializer.Serialize(new { IsUnauthenticated = true });
    filterContext.HttpContext.Response.Write(json);

    filterContext.HttpContext.Response.End();
}

If this does not work; your authentication middleware could be also responsible for setting this redirect and that would be set elsewhere unfortunately.

Sign up to request clarification or add additional context in comments.

Comments

0

My other answer was wrong, I have just tested it.

The solution to the problem is slightly different to what previous solution I have given you. Your middleware is the one causing the issue.

I suspect you're using what I'm using; Microsoft.ASPNET.Identity.

Inside your Startup.cs, you need to add OnApplyRedirect delegate.

Your code should look similar to mine:

app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
                     OnApplyRedirect = ctx =>
                     {
                         // add json response here...
                         ctx.RedirectUri = null;

                     }
                }
            });

From your original handler, move the response and add it to ctx.Response...

Hopefully this will put it back on track. Inside OnApplyRedirect you might want to check if it's ajax request then disable the redirect otherwise you'll get the ugly asp default error pages.. :-)

2 Comments

Thanks for the answer. I have just been looking though your answers, and your other (deleted) answer works for me. But only the SuppressFormsAuthenticationRedirect = true part, as Redirect(null) actually throws an error as you can't use null value. I am not entirely sure what this answer does but I am concerned it would have other undesired effects, and being as the other answer is a lot simpler and seems to do the job I will go with that instead. Let me know if you undelete it so I can reward it accordingly. Again, I appreciate your time in helping me out
@musefan I've undeleted the answer. I think my answers are based on different scenarios as I have ran some tests on actual projects and both showed different result. As I said previously it all depends on your project setup and middleware etc. Glad one way worked, I have undeleted the correct answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.