This post is about a quite common problem that I’ve encountered over and over again but didn’t look into it to find a proper answer to it. The problem is what happens when I request via AJAX a page that makes a HTTP redirect to another and how do I fix it to behave “normally”. It’s quite common problem with pages that require authentication.
Let’s say we have the following PHP script:
if( !is_user_allowed() ) {
header( 'Location: ' . PATH_TO_LOGIN_PAGE );
die();
}
Pretty simple and self explanatory. If the user isn’t logged in, he gets sent to the login form. But what happens if the request is made via AJAX? A simple AJAX request made using the prototype.js library looks like this:
new Ajax.Request(
'server_side.php',
{
onSuccess: function( t ) {
$( 'container' ).update( t.responseText );
}
}
);
What happens if the user isn’t logged in? The browser makes two requests to the server, the second one to the page containing the login form.
And the whole login page gets loaded into a small container on current page, ruining the design and confusing the user.
Although I’ve encountered this problem on several occasions, I didn’t give it much thought. I’ve used that very popular design pattern “I know it’s lame, but hey, it works…moving on”. The implementation on case was like this: add a token to the login page’s markup, something like and check to see if this string is in the response text. Like this:
new Ajax.Request(
'server_side.php',
{
onSuccess: function( t ) {
if( t.responseText.indexOf( '<!--login-->') != -1 ) {
document.location = 'login.php';
}
else {
$( 'container' ).update( t.responseText );
}
}
}
);
Plain and simple. And lame. I admit it. But hey! It works
Still, I’ve found a better way of doing things, that relies on HTTP headers. First of all, in the login page, instead of a lame string message, I’ve added some custom headers to the response.
header( 'HTTP/1.0 401 Authorization Required' ); header( 'Login-path: ' . PATH_TO_LOGIN_PAGE );
And then, I’ve altered the the javascript a little. Since a response served with a 401 header won’t trigger the onSuccess callback function, this script uses onComplete.
new Ajax.Request(
'server_side.php',
{
onComplete: function( t ) {
switch( t.status ) {
case 200:
$( 'container' ).update( t.responseText );
break;
case 401:
document.location = t.getHeader( 'Login-path' );
break;
}
}
}
)
It works. And it’s not so lame. Mission accomplished…
If you’re using jQuery to post AJAX requests you may test server-side if the request was made via AJAX:
function isAjax() { return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')); }then you can display an message in the ‘content’ box:
if( !is_user_allowed() ) { if (!isAjax()){ header( 'Location: ' . PATH_TO_LOGIN_PAGE ); die(); } else { die('User not allowed.'); } }Just an alternative way. Maybe other JavaScript frameworks support this too.
Most of them do. It’s a common practice nowadays to add the HTTP_X_REQUESTED_WITH header to the request. It think prototype did it first
And a lot of PHP framework support this detection:
The thing is that I don’t want to simply display the “user not allowed” message, I want to redirect the user to the login form, even if the request is made via Ajax and not just by get or post…
Just a very little observation: instead of “case 401:”, I would write “default:”
There are more HTTP statuses than just those two…