Click here to Skip to main content
15,867,686 members
Articles / Web Development / ASP.NET

Handling authentication-specific issues for AJAX-calls

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
18 Sep 2013CPOL4 min read 84.3K   18   12
For modern web-applications has become the usual to use AJAX when you create user interfaces. However, it makes our headache from time to time. And often these difficulties are associated with authentication and processing such requests on the client.

Introduction

For modern web-applications has become the usual to use AJAX when you create user interfaces. However, it makes our headache from time to time. And often these difficulties are associated with authentication and processing such requests on the client.

The problem

Imagine, that you have some web-application which communicates with server by using jQuery and JSON.

Server side:

[HttpPost]
public ActionResult GetData()
{
	return Json(new
	{
		Items = new[]
		{
			"Li Chen",
			"Abdullah Khamir",
			"Mark Schrenberg",
			"Katy Sullivan",
			"Erico Gantomaro",
		}
	});
}

Client side:

var $list = $("#list");
var $status = $("#status");
$list.empty();
$status.text("Loading...");

$.post("/home/getdata")
	.always(function() {
		$status.empty();
	})
	.success(function(data) {
		for (var i = 0; i < data.Items.length; i++) {
			$list.append($("<li/>").text(data.Items[i]));
		}
	});

That is pretty simple. Let’s add a simple authentication to our web-application. And again it is a pretty simple – just use Forms Authentication and Authorize attribute from ASP.NET MVC. The controller’ code will change as:

[HttpPost]
[Authorize]
public ActionResult GetData()
{
	return Json(new
	{
		Items = new[]
		{
			"Li Chen",
			"Abdullah Khamir",
			"Mark Schrenberg",
			"Katy Sullivan",
			"Erico Gantomaro",
		}
	});
}

Still have no problems. Once user authenticated in application, he able to see the page and can retreive a data. However, there is a some problem when authentication timeout expired. In this case server will send to user…HTTP 302 Found.

HTTP 302 Found

Obviously, that the client code in our case did not expect this and as a result application will not work properly.

Reasons

Before we will fix the code, let’s understand what happens. Why server send HTTP 302? That would be logical to assume that there must be a HTTP 401. The point is that when we use FormsAuthentication at backstage acts FormsAuthenticationModule (which registered in global web.config file by default). Look under the hood of the module, you can easily understand that if the current code for HTTP is 401, then it performs a redirect, i.e. replaces it on 302:

Redirect

It is easy to guess that the purpose is to redirect a user to the login page, if the request is not processed successfully (status code is 401). In this case, the user will see the good login form, but not the IIS error code. Makes sense, doesn't it?

From the point of view of our ASP.NET MVC application a pipeline is something like this:

  1. A request comes in to the application, where faced a filter AuthorizeAttribute.
  2. Because of the user is not authenticated, then this filter will return the HTTP 401, which is logical (see this easily enough by using reflector and check implementation of this filter).
  3. Well, after that our FormsAuthenticationModule acts and replaces HTTP 401 with redirect.

As a result – when we trying to request a page with usual HTTP-request we will see login page (which is good), but when we use AJAX-calls, there is too hard to parse such answers (which is bad).

The solution

So, what we need to solve the problem –

  1. Server should return HTTP 401/403 for AJAX-calls and HTTP 302 for usual HTTP-calls.
  2. To handle HTTP 401/403 on a client-side.

To be honest, you can override FormsAuthenticationModule logic to don’t replace HTTP 401 request with 302. To do it you can use SuppressFormsAuthenticationRedirect propery:

SuppressFormsAuthenticationRedirect

The only question is when to modify this property and who will do it?

Before answering this question, let’s turn our attention to how client should handle cases with HTTP-errors. There is two ways –

  1. The user is not authenticated in application (401). In this case we should redirect him to a login page.
  2. The user is authenticated in application, but have no enough permissions to access to the resource (403). For example, user not in role, which is necessary for current HTTP-request. In this case it is too silly to redirect him to a login page. The more preferred way is to say him that he is not have a necessary permissions.

Thus we should handle both cases. Let’s take a look on AuthorizeAttribute once again.

AuthorizeAttribute

…i.e. he will always send HTTP 401.

Not good. Therefore we should fix a bit this behavior. So, let’s get started.

First – let’s determine whether current HTTP-request is AJAX-request. If yes, we should disable replacing HTTP 401 with HTTP 302:

public class ApplicationAuthorizeAttribute : AuthorizeAttribute
{
	protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
	{
		var httpContext = filterContext.HttpContext;
		var request = httpContext.Request;
		var response = httpContext.Response;

		if (request.IsAjaxRequest())
			response.SuppressFormsAuthenticationRedirect = true;

		base.HandleUnauthorizedRequest(filterContext);
	}
}

Second – let’s add a condition:: if user authenticated, then we will send HTTP 403; and HTTP 401 otherwise.

public class ApplicationAuthorizeAttribute : AuthorizeAttribute
{
	protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
	{
		var httpContext = filterContext.HttpContext;
		var request = httpContext.Request;
		var response = httpContext.Response;
		var user = httpContext.User;

		if (request.IsAjaxRequest())
		{
			if (user.Identity.IsAuthenticated == false)
				response.StatusCode = (int)HttpStatusCode.Unauthorized;
			else
				response.StatusCode = (int)HttpStatusCode.Forbidden;

			response.SuppressFormsAuthenticationRedirect = true;
			response.End();
		}

		base.HandleUnauthorizedRequest(filterContext);
	}
}

Well done. Now we should replace all usings of standard AuthorizeAttribute with this new filter. It may be not applicable for sime guys, who is aesthete of code. But I don’t know any other way. If you have, let’s go to comments, please.

The last, what we should to do – to add HTTP 401/403 handling on a client-side. We can use ajaxError at jQuery to avoid code duplication:

$(document).ajaxError(function (e, xhr) {
	if (xhr.status == 401)
		window.location = "/Account/Login";
	else if (xhr.status == 403)
		alert("You have no enough permissions to request this resource.");
});

The result –

  • If user is not authenticated, then he will be redirected to a login page after any AJAX-call.
  • If user is authenticated, but have no enough permissions, then he will see user-friendly erorr message.
  • If user is authenticated and have enough permissions, the there is no any errors and HTTP-request will be proceeded as ususal.

The only disadvantage is necessity to use ApplicationAuthorizeAttribute instead of standard AuthorizeAttribute. So, if you have some code which uses AuthorizeAttribute, then you should fix it.

Sources placed on github.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Russian Federation Russian Federation
Microsoft MVP, Visual C#

Comments and Discussions

 
QuestionQuery Pin
Member 1295552217-Jan-17 20:22
Member 1295552217-Jan-17 20:22 
GeneralMy vote of 5 Pin
Member 1228102622-Jan-16 3:40
Member 1228102622-Jan-16 3:40 
GeneralMy vote of 5 Pin
00pol009-Sep-15 19:42
00pol009-Sep-15 19:42 
QuestionNice , but I use only this part Pin
00pol009-Sep-15 19:41
00pol009-Sep-15 19:41 
QuestionServer cannot set status after HTTP headers have been sent Pin
ajbeaven26-Jul-15 9:17
ajbeaven26-Jul-15 9:17 
AnswerRe: Server cannot set status after HTTP headers have been sent Pin
ajbeaven26-Jul-15 9:51
ajbeaven26-Jul-15 9:51 
GeneralMy vote of 5 Pin
demispb12-Mar-15 5:44
demispb12-Mar-15 5:44 
QuestionSolution for .NET 4.0 Pin
Member 113980445-Mar-15 12:22
Member 113980445-Mar-15 12:22 
QuestionNice, but... Pin
Roberto Orellana15-Feb-14 16:03
Roberto Orellana15-Feb-14 16:03 
AnswerRe: Nice, but... Pin
Sergey Zwezdin27-Apr-14 0:45
Sergey Zwezdin27-Apr-14 0:45 
Hi Nefari0us!

Thank you for your kind of words!
If you check the article, you can see that SuppressFormsAuthenticationRedirect is exactly what I use and why I do it.
SuggestionAuthentication globally & opt out those you don't need Pin
Kishore P. Krishna24-Oct-13 1:40
Kishore P. Krishna24-Oct-13 1:40 
GeneralRe: Authentication globally & opt out those you don't need Pin
Sergey Zwezdin27-Apr-14 0:47
Sergey Zwezdin27-Apr-14 0:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.