Introduction
A problem and solution which I will describe here is a workaround for basic dialog of Basic Http Authentication. I was faced with such a problem when trying to unify authentication flow. Like most of us, I started from browsing of internet to find a solution, unfortunately none which I found have worked for my case. And here, I would like to share my experience in the hope that somebody will find it useful.
Problem
Generally, a problem is to suppress the basic http authentication dialog and show custom UI instead of the Basic browser dialog, just to make common user experience for cross browsers. For example, user experience in case of typing wrong user credentials at Chrome, FF or Opera browsers will be different from Internet Explorer and Safari.I.e IE or Safari will cancel bad request after a few attempts to authenticate user on server (in case if it is wrong) and web application can show some page like "Can not access to your account". Chrome, FF and Opera will do it infinitely (the request will in pending state) until user will give up and press cancel button. And another problem is how to log out in case of basic authentication.
My experiments were around configuration of Apache server, but I think it can easily be done for IIS or Nginx. Because then everything is around Basic Http Authentication.
The solution passed tests on IE (10, 11), Chrome 49, FF 44, Opera 36.
Solution
First, what has to be done is to configure Http server, in my case it was Apache. Usually, all static contents like images, scripts, html which do not require authentication are used without authentication. But for the solution, we will need an image (for example) under authentication.
- I created a directory on server put there small image and configured apache so that it requires authentication to the resource.
Here is an example of config for directory with name 'auth_required
'
Alias /auth_required /path/auth_required/
<Location /auth_required>
Options Indexes
Order allow,deny
Allow from all
Include "/your/path/here/an_auth_config.conf"
</Location>
- I created a free authenticated directory for custom authentication UI. The directory has a simple HTML page which cannot break security of the whole portal and present simple authentication page.
Here is an example of Apache config for directory auth.
# Unprotected resources
<Directory /path/auth>
AuthType None
</Directory>
Alias /auth /path/auth
<Location /auth>
# No Auth
AuthType None
#Require all granted
DirectoryIndex auth_test.html
</Location>
And that is all what we have to do on server side.
The main trick is implemented on client side.
Below, you will find samples of code with inline comments. The solution works fine for Internet Explorer, Chrome, FF and Opera, but unfortunately doesn't for Safari.
I imagine that Safari as well will work if the server will provide 403 code instead of 401 in case of wrong user credentials. For now, I didn't find the right way for Safari.
Also, I have an open question where to keep user name and should the client do that in case when user successfully authenticated and next time automatically redirect him to main page without prompting auth page. For now, I keep user name at cookies, and if it is not empty, it is a trigger to redirect to main page automatically without providing user name and password directly at url.
function doLoad()
{
var user = $.cookie('loggedas');
if (user && user.length > 0)
{
authenticate();
}
}
function doAuthenticate()
{
var user = $('#useridUIID').val();
var pswd = $('#pswdUIID').val();
user = (user && user.length > 0 ? user : '' + Date.now().getTime())
authenticate(user, pswd);
}
function authenticate(user, pswd)
{
var warning = $('div.warning');
if (!warning.hasClass('hidden'))
{
warning.addClass('hidden');
}
var img = location.protocol + '//' + location.host +
(location.port && location.port.length > 0 ? ':' +
location.port : '') + '/auth_required/favicon.ico';
var xhr = new XMLHttpRequest();
if (user)
{
xhr.open('GET', img, true, user, pswd);
}
else
{
xhr.open('GET', img, true);
}
xhr.onreadystatechange = function (e)
{
if (this.status !== 0)
{
if (this.status === 200)
{
$.cookie('loggedas', user);
if (user)
{
try
{
document.location.href = location.protocol + '//' +
user + ':' + pswd + '@' + location.host +
(location.port && location.port.length > 0 ? ':' +
location.port : '') + '/admin/';
}
catch (e)
{
document.location.href = location.protocol + '//' +
location.host + (location.port && location.port.length > 0 ?
':' + location.port : '') + '/admin/';
}
}
else
{
document.location.href = location.protocol + '//' + location.host +
(location.port && location.port.length > 0 ? ':' +
location.port : '') + '/admin/';
}
}
else
{
if (warning.hasClass('hidden'))
{
warning.removeClass('hidden');
}
}
}
};
xhr.send();
}
$(document).ready(function ()
{
doLoad();
});
Now, you can authenticate to your server like this:
Instead of default:
Conclusion
Of course, everything that I said will work in case you don't want to implement custom authentication on server side and just use the basic one.
Also still I don't know an appropriate workaround for Safari. And I didn't test it for older browsers, does the solution work for them or not?
I will appreciate any comment on the topic. Thank you.