Projects Tech Stuff

Updated: Login/Logout Method for Basic Authentication

The situation: a kiosk application where people in the community would log in to a web page, request some materials, then log out again. The users are all in our Active Directory login system. How can you construct it so that a user can log out, and the next log in, without forcing them to close and re-open the browser?

Not so simple, but doable. Here’s the method I used with Linux, PHP and JavaScript. Bear in mind, the process is that a user logs in, does some work, and then clicks a link to log out. This will go to a new page we’ll create called “logout.php”.

.htaccess

First you have to set up your .htaccess file to properly lock users out until they log in with their Active Directory or LDAP credentials. Here’s what my file looks like, sanitized for my protection:

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
</IfModule>
AuthType Basic
AuthName "Media Manager"
Order deny,allow
Deny from All
AuthLDAPAuthoritative on
AuthLDAPBindDN "cn=yourusername,OU=SHU_Users,DC=yourdomain,DC=com"
AuthLDAPBindPassword "yourpassword"
AuthLDAPEnabled on
AuthLDAPURL ldap://yourldapserver.yourdomain.com/OU=Users,DC=yourdomain,DC=com?sAMAccountName?sub?(objectClass=person)
Require valid-user
Satisfy any

Fill in a couple of blanks here. You will absolutely need the cooperation of whoever manages your ldap server to get the “yourusername” and “yourpassword” entries. These will be credentials that allow your application to talk to the ldap server. Other values in the AuthLDAPURL string may vary too. This is probably the single hardest part — untangling the cryptic parameters you need to pass to get the information you need.

But once you have it, save it into the subdirectory your web application lives in as .htaccess.

Logging Out, Two Ways

Would you be surprised to learn that you have to do this differently in Internet Explorer than you do in Firefox? Me neither. But in this case, the IE version is simpler. You can do it with a simple JavaScript command. First, you need to have jQuery installed.

Once you have it, create a file called “logout.php” and put it in the same directory as your .htaccess file. start out the file with the usual opening <php?, and add this code:

$CurrentUrl         = 'the.full.path/to/logout.php';
// Status flags:
$LoginSuccessful    = false;
$Logout             = false;
// Check username and password:
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])){
    $usr = $_SERVER['PHP_AUTH_USER'];
    $pwd = $_SERVER['PHP_AUTH_PW'];
    // Does the user want to login or logout?
    if ($usr != 'reset' && $pwd != 'reset'){
        $LoginSuccessful = true;
    }
    else if ($usr == 'reset' && $pwd == 'reset' && isset($_GET['Logout'])){
        // reset is a special login for logout
        $Logout = true;
    }
}
if ($Logout){
     ?>

We’re setting up some basic variables, setting initial statuses to false, and then pulling out the username and password from the credentials the user has already entered when they originally logged in to use your application.

If the username and password are both “reset,” bogus entries that I’ll explain below, the user is attempting to log out with an ajax request. If that’s the case, then we generate one set of HTML. It can be as simple as:

&lt;html>
&lt;head>
&lt;title>Logged Out&lt;/title>
&lt;body>
&lt;?
    // The user clicked on "Logout"
    print 'You are now logged out.';
    print '&lt;br/>';
    print '&lt;a href="http://'.$CurrentUrl.'">Login again&lt;/a>';
 ?>
&lt;/body>
&lt;/html>

The First Pass

The first time a user hits the logout.php page, he’ll already be logged in, and his credentials are sitting in the cache. The value of $LoginSuccessful will have been set to “true” in the initial block of PHP code, and so you’ll need to give them something else.

The real credentials passed in — which by definition they are, since your user had to log in to get here in the first place — means they’ve clicked your link to “logout.php” for the first time and you need to show them the entire page.

Call in the jQuery libraries:

&lt;script src="js/jquery-1.6.3.min.js" type="text/javascript">&lt;/script>
&lt;script src="js/jquery-ui-1.8.16.custom.min.js" type="text/javascript">&lt;/script>

Then, drop this into your <head> section. Note that this has been updated:

&lt;script type="text/javascript" language="javascript">
function LogMeOff() {
  /**** OLD CODE
  try {
    document.execCommand("ClearAuthenticationCache");
    alert('Logged Off');
    } catch (exception) {
    //not an IE browser
    alert('Logging Out...');
    $.ajax({
      url: 'http://the.full.path/to/logout.php?Logout=1',
      username: 'reset',
      password: 'reset',
      statusCode: { 401: function() { alert('Logged Out') } }
      });
    } **** END OLD CODE */

    /**** NEW CODE */
  try {
     $.ajax({
        url: 'http://tltc.shu.edu/mediamanager/logout.php?Logout=1',
            username: 'reset',
            password: 'reset',
            statusCode: { 401: function() { alert('Logged Out') } }
        });
        alert('Logged Off');
        } catch (exception) {
            alert('Logging Out...');
            document.execCommand("ClearAuthenticationCache");
     } /**** END NEW CODE */
  return false;
}
&lt;/script>

What wass this doing? First it tried to execute the “ClearAuthenticationCache” command, which only exists in Internet Explorer. It’s in a try/catch block so if it works, it’s done. If it does not work, that means you’re not using IE and something else needs to happen.

With the latest versions of Firefox, the call to ClearAuthenticatonCache didn’t throw an error, so the catch block was never called. By reversing the order, it now works in IE9 and Firefox 15.

The jQuery ajax call I mentioned above is made in the “catch” block to the same page you’re on, passing in the bogus username/password combination of “reset” and “reset”, then sending a 401 status code — “Not Authorized.” This is what has the effect of clearing out the Firefox authentication cache by replacing the values that work — the user’s original login and password — with values that won’t — “reset” and “reset”.

It’s triggered by a single line in the HTML of the page:

    print '&lt;a onclick="LogMeOff()">Logout&lt;/a>';

The last thing that’s needed is a way to catch other kinds of requests, such as the user entering the wrong combination three times, or giving up.

    header('WWW-Authenticate: Basic realm="Top-secret area"');
    header('HTTP/1.0 401 Unauthorized');
    // Error message
    print "Sorry, login failed!\n";
    print "&lt;br/>";
    print '&lt;a href="http://' . $CurrentUrl . '">Try again&lt;/a>';

Either way, the user should be logged out, and a new (correct) login and password will have to be passed in for the application to work again. Here’s the complete code of the page.

&lt;?
$CurrentUrl         = 'the.full.path/to/logout.php';
// Status flags:
$LoginSuccessful    = false;
$Logout             = false;
// Check username and password:
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])){
    $usr = $_SERVER['PHP_AUTH_USER'];
    $pwd = $_SERVER['PHP_AUTH_PW'];
    // Does the user want to login or logout?
    if ($usr != 'reset' && $pwd != 'reset'){
        $LoginSuccessful = true;
    }
    else if ($usr == 'reset' && $pwd == 'reset' && isset($_GET['Logout'])){
        $Logout = true;
    }
}
if ($Logout){
     ?>
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
&lt;html xmlns="http://www.w3.org/1999/xhtml">
&lt;head>
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
&lt;title>Example | Logged Out&lt;/title>
&lt;script src="js/jquery-1.6.3.min.js" type="text/javascript">&lt;/script>
&lt;script src="js/jquery-ui-1.8.16.custom.min.js" type="text/javascript">&lt;/script>
&lt;/head>
&lt;body>
&lt;?
    // The user clicked on "Logout"
    print 'You are now logged out.';
    print '&lt;br/>';
    print '&lt;a href="http://'.$CurrentUrl.'">Login again&lt;/a>';
&lt;/body>
&lt;/html>
    &lt;?
}
else if ($LoginSuccessful){
 ?>
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
&lt;html xmlns="http://www.w3.org/1999/xhtml">
&lt;head>
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
&lt;title>Example | Log Out&lt;/title>
&lt;script src="js/jquery-1.6.3.min.js" type="text/javascript">&lt;/script>
&lt;script src="js/jquery-ui-1.8.16.custom.min.js" type="text/javascript">&lt;/script>
&lt;script type="text/javascript" language="javascript">
function LogMeOff() {
    try {
       document.execCommand("ClearAuthenticationCache");
       alert('Logged Off');
        } catch (exception) {
            //not an IE browser
            alert('Logging Out...');
            $.ajax({
                   url: 'http://the.full.path/to/logout.php?Logout=1',
                   username: 'reset',
                   password: 'reset',
                   statusCode: { 401: function() { alert('Logged Out') } }
                   });
     }
     return false;
}
&lt;/script>
&lt;/head>
&lt;body>
&lt;?
    // The user entered the correct login data, put
    // your confidential data in here:
    print 'Click the link below to log out of the system. &lt;br/>';
    print '&lt;br/>';
    // This will not clear the authentication cache, but
    // it will replace the "real" login data with bogus data
    print '&lt;a onclick="LogMeOff()">Logout&lt;/a>';
    include_once('includes/foot.php')
    ?>
    &lt;/body>
    &lt;/html>
    &lt;?
}
else {
    /*
    ** The user gets here if:
    **
    ** 1. The user entered incorrect login data (three times)
    **     --> User will see the error message from below
    **
    ** 2. Or the user requested the page for the first time
    **     --> Then the 401 headers apply and the "login box" will
    **         be shown
    */
    // The text inside the realm section will be visible for the
    // user in the login box
    header('WWW-Authenticate: Basic realm="Top-secret area"');
    header('HTTP/1.0 401 Unauthorized');
    // Error message
    print "Sorry, login failed!\n";
    print "&lt;br/>";
    print '&lt;a href="http://' . $CurrentUrl . '">Try again&lt;/a>';
    ?> 
&lt;/body>
&lt;/html>
&lt;?
}
Tom

Tom McGee has been building web sites since 1995, and blogging here since 2006. Currently a senior developer at Seton Hall University, he’s also a freelance web programmer and musician. Contact him if you have the need for a blog, web site, redesign or custom programming!

6 thoughts on “Updated: Login/Logout Method for Basic Authentication”

  1. hai i am umapathyvel, thanks for your coding providing logout functionality. but how can i do this action in google chrome. it doesnt work on google chrome. could u suggest any idea for working onto that?

  2. Thanks for this code! I’ve posted a modified version of it which also works in Chrome here:
    http://patabugen.co.uk/2013/12/09/httpauth-logout-in-chrome-firefox-and-ie-with-jquery/

    $(function(){
        $('#user_logout').on('click', function(e){
            // HTTPAuth Logout code based on: http://tom-mcgee.com/blog/archives/4435
            e.preventDefault();
            try {
                // This is for Firefox
                $.ajax({
                    // This can be any path on your same domain which requires HTTPAuth
                    url: "/any/path",
                    username: 'reset',
                    password: 'reset',
                    // If the return is 401, refresh the page to request new details.
                    statusCode: { 401: function() {
                        document.location = document.location;
                        }
                    }
                });
            } catch (exception) {
                // Firefox throws an exception since we didn't handle anything but a 401 above
                // This line works only in IE
                if (!document.execCommand("ClearAuthenticationCache")) {
                    // exeCommand returns false if it didn't work (which happens in Chrome) so as a last
                    // resort refresh the page providing new, invalid details.
                    document.location = "http://reset:reset@" + document.location.hostname + document.location.pathname;
                }
            }
        });
    });
  3. If you’re using https, be sure all the document.location etc. links are set that way also, or the previous authentication will stick.

Leave a Reply

Your email address will not be published. Required fields are marked *