Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / Apache

Customization of Basic Http Authentication

5.00/5 (1 vote)
8 Apr 2016CPOL3 min read 19.9K  
Suppression of Browser dialog of Basic Http Authentication and customization

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.

  1. 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>
  2. 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.

JavaScript
function doLoad()
{
    //the method will be called on page load and in case 
    //if user previously was authenticated should redirect to main page
    //here we have to get logged in user somehow, probably from cookie or local storage.

    //we always have to provide user name to XMLHttpRequest even if it is not correct, 
    //because in case if user was not logged in before 
    //and we do not provide any user name for XMLHttpRequest.open method 
    //default authentication window will be prompted, but we want to suppress it

    var user = $.cookie('loggedas');
    if (user && user.length > 0)
    {
        authenticate();
    }
}

function doAuthenticate()
{
    //the method have to be called on user click on user credentials form
    var user = $('#useridUIID').val();
    var pswd = $('#pswdUIID').val();

    //it is main trick, default authenticated window will be 
    //prompted until ANY user name (even not correct) will be passed to XMLHttpRequest
    //but it doesn't work for Safari
    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');
    }

    //path to resource which require authentication
    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) //work around for IE
        {
            if (this.status === 200)
            {
                //keep user name, in order to redirect automatically next time
                //from my perspective I dont see any security breach to keep user name at cookies, 
                //if it is not so, here should be another way to automatically redirecting next time
                $.cookie('loggedas', user);

                //redirect to main page
                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
            {
                //show error in case wrong credentials
                if (warning.hasClass('hidden'))
                {
                    warning.removeClass('hidden');
                }
            }
        }
    };

    xhr.send();
}

$(document).ready(function ()
{
    doLoad();
});

Now, you can authenticate to your server like this:

Image 1

Instead of default:

Image 2

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.

License

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