157

What is the correct way to log out of HTTP authentication protected folder?

There are workarounds that can achieve this, but they are potentially dangerous because they can be buggy or don't work in certain situations / browsers. That is why I am looking for correct and clean solution.

bignose
  • 30,281
  • 14
  • 77
  • 110
Josef Sábl
  • 7,538
  • 9
  • 54
  • 66
  • Please specify the purpose for your logout. Should this be a forced logout (user-deactivation)? Simple logout function for the user? Anything else? – Karsten Jan 16 '09 at 09:38
  • 6
    I don't understand why this matters, but it is both cases: deactivation based on internal conditions in application as well as typical logout button. Please explain why it is important, I will edit it directly into the question. – Josef Sábl Jan 16 '09 at 13:02
  • 2
    The "correct and clean solution" would be browsers having their own logout button that, when clicked, will make the browser stop sending the Auth headers... One can dream, right? – DanMan Apr 21 '12 at 17:18
  • 1
    Web Developer Toolbar has such "button". – Josef Sábl Apr 23 '12 at 07:37
  • What Josef said: [web developer toolbar for Firefox](https://addons.mozilla.org/en-US/firefox/addon/web-developer/) -> `Miscellaneous -> Clear Private Data -> HTTP Authentication` – Yarin Jun 16 '12 at 22:15

19 Answers19

104

Mu. No correct way exists, not even one that's consistent across browsers.

This is a problem that comes from the HTTP specification (section 15.6):

Existing HTTP clients and user agents typically retain authentication information indefinitely. HTTP/1.1. does not provide a method for a server to direct clients to discard these cached credentials.

On the other hand, section 10.4.2 says:

If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information.

In other words, you may be able to show the login box again (as @Karsten says), but the browser doesn't have to honor your request - so don't depend on this (mis)feature too much.

Community
  • 1
  • 1
Piskvor left the building
  • 91,498
  • 46
  • 177
  • 222
62

Method that works nicely in Safari. Also works in Firefox and Opera, but with a warning.

Location: http://logout@yourserver.example.com/

This tells browser to open URL with new username, overriding previous one.

Kornel
  • 97,764
  • 37
  • 219
  • 309
  • 14
    According to RFC 3986 (URI: Generic Syntax) section 3.2.1. (User Information) the use of `user:password@host` is deprecated. Using only `http://logout@yourserver.example.com/` isn't and should work in most cases. – aef Apr 28 '11 at 12:55
  • Deprecated generally means that something should not be used as it may or will be removed at some point. This may work for you now but should you use it be prepared for it to stop working at some point. Good solution though. – Night Owl Aug 02 '11 at 05:22
  • 1
    @andho: yes, it's a redirect. You should use it with status 302. – Kornel Aug 06 '11 at 11:16
  • 1
    Apparently a simple link to http://logout@yourserver.example.com/ also works (a "disconnect" link to this URL) instead of an http redirect in PHP... any downside to that? – moala Mar 21 '12 at 13:40
  • 4
    Beware: Form submission using relative path might fail when it is done after a re-login(login with the logout prompt), because the address would still be [logout@yourserver.example.com/path](http://logout@yourserver.example.com/path/) and not [yourserver.example.com/path/](http://yourserver.example.com/path/) – Jason Apr 11 '12 at 21:55
  • 1
    http://logout@yourserver.example.com works whitout issue in Chrome, but prompts a security quistion in Firefox. http://logout:true@yourserver.example.com dose not make Firefox promt a security quistion. Neither of the two urls work in IE8 :/ – Thor A. Pedersen Jun 26 '12 at 12:15
  • +1 for working hack, which is all we really need ! When you're testing 100 user accounts for your organization on say a VPN, , it sortof saves your day. – Peter Host Aug 04 '12 at 20:42
  • On Chrome 45, after logging out and logging in again, prompt is always popping-up... – PJunior Sep 12 '15 at 10:50
47

The simple answer is that you can't reliably log out of http-authentication.

The long answer:
Http-auth (like the rest of the HTTP spec) is meant to be stateless. So being "logged in" or "logged out" isn't really a concept that makes sense. The better way to see it is to ask, for each HTTP request (and remember a page load is usually multiple requests), "are you allowed to do what you're requesting?". The server sees each request as new and unrelated to any previous requests.

Browsers have chosen to remember the credentials you tell them on the first 401, and re-send them without the user's explicit permission on subsequent requests. This is an attempt at giving the user the "logged in/logged out" model they expect, but it's purely a kludge. It's the browser that's simulating this persistence of state. The web server is completely unaware of it.

So "logging out", in the context of http-auth is purely a simulation provided by the browser, and so outside the authority of the server.

Yes, there are kludges. But they break RESTful-ness (if that's of value to you) and they are unreliable.

If you absolutely require a logged-in/logged-out model for your site authentication, the best bet is a tracking cookie, with the persistence of state stored on the server in some manner (mysql, sqlite, flatfile, etc). This will require all requests to be evaluated, for instance, with PHP.

Jonathan Hanson
  • 4,661
  • 1
  • 19
  • 16
27

Workaround

You can do this using Javascript:

<html><head>
<script type="text/javascript">
function logout() {
    var xmlhttp;
    if (window.XMLHttpRequest) {
          xmlhttp = new XMLHttpRequest();
    }
    // code for IE
    else if (window.ActiveXObject) {
      xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    if (window.ActiveXObject) {
      // IE clear HTTP Authentication
      document.execCommand("ClearAuthenticationCache");
      window.location.href='/where/to/redirect';
    } else {
        xmlhttp.open("GET", '/path/that/will/return/200/OK', true, "logout", "logout");
        xmlhttp.send("");
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == 4) {window.location.href='/where/to/redirect';}
        }


    }


    return false;
}
</script>
</head>
<body>
<a href="#" onclick="logout();">Log out</a>
</body>
</html>

What is done above is:

  • for IE - just clear auth cache and redirect somewhere

  • for other browsers - send an XMLHttpRequest behind the scenes with 'logout' login name and password. We need to send it to some path that will return 200 OK to that request (i.e. it shouldn't require HTTP authentication).

Replace '/where/to/redirect' with some path to redirect to after logging out and replace '/path/that/will/return/200/OK' with some path on your site that will return 200 OK.

nickhar
  • 19,981
  • 12
  • 60
  • 73
Anton Mochalin
  • 360
  • 4
  • 4
  • 5
    It's somewhat of a workaround to login as another user. But this actually works and deserves more credit. – Charlie Rudenstål Apr 15 '13 at 14:09
  • 2
    I think that this is the best answer. As stated in [this](http://stackoverflow.com/a/14329930/2433501) answer to a similar question there may be some advantage to randomizing the password. – zelanix Mar 06 '14 at 20:52
  • 2
    This was what I wanted - worked in all browsers with no problems. Kept the "logout" page I inherited intact. I didn't necessarily want to use JS (maybe irrationally), but the other answers all had cross-browser issues and this worked perfect. – dmgig May 16 '14 at 14:44
  • I can't make this work in the way is explained. When I went back to the secured area, the browser authenticates itself again with sending the last used valid credentials in the header. However, with a little change it worked for me. I changed the 200 OK response with a header with the same Realm of the secured area, but accepting only a "logout:logout" user/pass. In this way, the user logged in with this "logout" user, and this is the user that retries when he go back to the secured area. Secured area reject this user/pass, so the user can change its credentials. – jonaguera Feb 17 '15 at 09:55
  • 2
    This does not work as it is explained. Tested in Chrome 40 and Firefox 35. – funforums Feb 26 '15 at 15:16
  • Works great in Safari 9 – Matthieu Oct 22 '15 at 12:45
  • \o/\o/ Outstanding solution!! Thank you very much. Works perfectly in Chrome 70.0.3538, Internet Explorer 11.345 and Microsoft Edge. The most important... it is silent, because it does't show the browser's prompt asking you to provide username/password. The clever trick is to use the 4th and 5th parameters in xmlhttp.open(). – MMJ Dec 12 '18 at 15:31
13

Workaround (not a clean, nice (or even working! see comments) solution):

Disable his credentials one time.

You can move your HTTP authentication logic to PHP by sending the appropriate headers (if not logged in):

Header('WWW-Authenticate: Basic realm="protected area"');
Header('HTTP/1.0 401 Unauthorized');

And parsing the input with:

$_SERVER['PHP_AUTH_USER'] // httpauth-user
$_SERVER['PHP_AUTH_PW']   // httpauth-password

So disabling his credentials one time should be trivial.

Chris Wesseling
  • 6,226
  • 2
  • 36
  • 72
Karsten
  • 14,572
  • 5
  • 30
  • 35
  • 18
    The problem with this solution is that: You let IE to know that credentials are not Ok. It displays login dialog with empty fields (not showing values stored in password manager). But when you click cancel and refresh the page, it sends stored credentials, thus logging in again. – Josef Sábl Jan 19 '09 at 09:38
  • Downvoted; Like Josef Sable commented, this doesn't solve the problem at hand. – Chris Wesseling Jan 18 '12 at 09:31
7

Logout from HTTP Basic Auth in two steps

Let’s say I have a HTTP Basic Auth realm named “Password protected”, and Bob is logged in. To log out I make 2 AJAX requests:

  1. Access script /logout_step1. It adds a random temporary user to .htusers and responds with its login and password.
  2. Access script /logout_step2 authenticated with the temporary user’s login and password. The script deletes the temporary user and adds this header on the response: WWW-Authenticate: Basic realm="Password protected"

At this point browser forgot Bob’s credentials.

Community
  • 1
  • 1
Vlad GURDIGA
  • 1,284
  • 2
  • 14
  • 18
7

My solution to the problem is the following. You can find the function http_digest_parse , $realm and $users in the second example of this page: http://php.net/manual/en/features.http-auth.php.

session_start();

function LogOut() {
  session_destroy();
  session_unset($_SESSION['session_id']);
  session_unset($_SESSION['logged']);

  header("Location: /", TRUE, 301);   
}

function Login(){

  global $realm;

  if (empty($_SESSION['session_id'])) {
    session_regenerate_id();
    $_SESSION['session_id'] = session_id();
  }

  if (!IsAuthenticated()) {  
    header('HTTP/1.1 401 Unauthorized');
    header('WWW-Authenticate: Digest realm="'.$realm.
   '",qop="auth",nonce="'.$_SESSION['session_id'].'",opaque="'.md5($realm).'"');
    $_SESSION['logged'] = False;
    die('Access denied.');
  }
  $_SESSION['logged'] = True;  
}

function IsAuthenticated(){
  global $realm;
  global $users;


  if  (empty($_SERVER['PHP_AUTH_DIGEST']))
      return False;

  // check PHP_AUTH_DIGEST
  if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
     !isset($users[$data['username']]))
     return False;// invalid username


  $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
  $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);

  // Give session id instead of data['nonce']
  $valid_response =   md5($A1.':'.$_SESSION['session_id'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);

  if ($data['response'] != $valid_response)
    return False;

  return True;
}
Pie86
  • 505
  • 5
  • 15
5

The only effective way I've found to wipe out the PHP_AUTH_DIGEST or PHP_AUTH_USER AND PHP_AUTH_PW credentials is to call the header HTTP/1.1 401 Unauthorized.

function clear_admin_access(){
    header('HTTP/1.1 401 Unauthorized');
    die('Admin access turned off');
}
CIRCLE
  • 4,501
  • 5
  • 37
  • 56
4

Typically, once a browser has asked the user for credentials and supplied them to a particular web site, it will continue to do so without further prompting. Unlike the various ways you can clear cookies on the client side, I don't know of a similar way to ask the browser to forget its supplied authentication credentials.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • I believe there's an option to delete authenticated sessions when you select "Delete private data" in Firefox – Kristian J. Jan 16 '09 at 09:02
  • 1
    Also Web Developer Toolbar extension for Firefox offers feature to delete HTTP Authentications. But this is out of question as we really can't ask our users to download FF extensions or run cryptic browser commands :-) – Josef Sábl Mar 03 '09 at 21:28
  • 2
    Firefox's default way of logging out of HTTP auth is available under "Tools" > "Clear Recent History...", as checkbox "Active Logins". This is neither intuitive nor does it allow you to only log out of one domain, you allways log out of every page. – aef Apr 28 '11 at 13:06
2

This might be not the solution that was looked for but i solved it like this. i have 2 scripts for the logout process.

logout.php

<?php
header("Location: http://.@domain.com/log.php");
?>

log.php

<?php
header("location: https://google.com");
?>

This way i dont get a warning and my session is terminated

Kevin
  • 21
  • 2
2

Trac - by default - uses HTTP Authentication as well. Logout does not work and can not be fixed:

  • This is an issue with the HTTP authentication scheme itself, and there's nothing we can do in Trac to fix it properly.
  • There is currently no workaround (JavaScript or other) that works with all major browsers.

From: http://trac.edgewall.org/ticket/791#comment:103

Looks like that there is no working answer to the question, that issue has been reported seven years ago and it makes perfect sense: HTTP is stateless. Either a request is done with authentication credentials or not. But that's a matter of the client sending the request, not the server receiving it. The server can only say if a request URI needs authorization or not.

hakre
  • 193,403
  • 52
  • 435
  • 836
2

I needed to reset .htaccess authorization so I used this:

<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My Realm"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Text to send if user hits Cancel button';
    exit;
}
?>

Found it here : http://php.net/manual/en/features.http-auth.php

Go figure.

A number of solutions reside on that page and it even notes at the bottom: Lynx, doesn't clear the auth like other browsers ;)

I tested it out on my installed browsers and once closed, each browser seems like it consistently requires reauth on reentry.

Dooley
  • 41
  • 5
  • This doesn't seem to be working, I am getting the cancel text without any pop-up login box. – Michael Dec 10 '14 at 18:27
  • Turns out, sending the `WWW-Authenticate` was causing the problem, getting rid of that logged me out automatically. – Michael Dec 10 '14 at 18:32
  • And conversely, it seems that *NOT* sending the `WWW-Authenticate` while fixing the problem in one browser (Chrome) causes another browser (Firefox) to remember the credentials and send them on the next request, resulting in an automatic re-login! Argh! – Michael Dec 16 '14 at 17:02
  • Then look at UA and do one or the other seems like a solution – Mr. Developerdude Nov 21 '15 at 00:19
1

Maybe I'm missing the point.

The most reliable way I've found to end HTTP Authentication is to close the browser and all browser windows. You can close a browser window using Javascript but I don't think you can close all browser windows.

Toby Allen
  • 10,997
  • 11
  • 73
  • 124
  • fyi some browsers won't close a window if it's the only tab open, so the point is moot really – scape Jul 01 '13 at 16:06
  • Those long years ago I had task to implement the logout button without closing the window :-) But maybe they would not dwell on "not closing the window". But hey, this is simple solution that might work for somebody and I missed it back then to be honest. – Josef Sábl Oct 19 '17 at 11:10
1

AFAIK, there's no clean way to implement a "logout" function when using htaccess (i.e. HTTP-based) authentication.

This is because such authentication uses the HTTP error code '401' to tell the browser that credentials are required, at which point the browser prompts the user for the details. From then on, until the browser is closed, it will always send the credentials without further prompting.

Alnitak
  • 334,560
  • 70
  • 407
  • 495
1

The best solution I found so far is (it is sort of pseudo-code, the $isLoggedIn is pseudo variable for http auth):

At the time of "logout" just store some info to the session saying that user is actually logged out.

function logout()
{
  //$isLoggedIn = false; //This does not work (point of this question)
  $_SESSION['logout'] = true;
}

In the place where I check for authentication I expand the condition:

function isLoggedIn()
{
  return $isLoggedIn && !$_SESSION['logout'];
}

Session is somewhat linked to the state of http authentication so user stays logged out as long as he keeps the browser open and as long as http authentication persists in the browser.

Josef Sábl
  • 7,538
  • 9
  • 54
  • 66
0

There's a lot of great - complex - answers here. In my particular case i found a clean and simple fix for the logout. I have yet to test in Edge. On my page that I have logged in to, I have placed a logout link similar to this:

<a href="https://MyDomainHere.net/logout.html">logout</a>

And in the head of that logout.html page (which is also protected by the .htaccess) I have a page refresh similar to this:

<meta http-equiv="Refresh" content="0; url=https://logout:logout@MyDomainHere.net/" />

Where you would leave the words "logout" in place to clear the username and password cached for the site.

I will admit that if multiple pages needed to be able to be directly logged in to from the beginning, each of those points of entry would need their own corresponding logout.html page. Otherwise you could centralize the logout by introducing an additional gatekeeper step into the process before the actual login prompt, requiring entry of a phrase to reach a destination of login.

johnwayne
  • 41
  • 2
  • 7
  • 1
    when moving forward, this works, it does log out, but the browser back history can still re-establish the session. – johnwayne Oct 18 '17 at 11:00
0

I have summarised my solution in an article (https://www.hattonwebsolutions.co.uk/articles/how_to_logout_of_http_sessions) however I have used an ajax call and 2x htaccess files (as suggested in this question: How to logout of an HTTP authentication (htaccess) that works in Google Chrome?).

In short - you:

  1. Create a sub folder with an htaccess file on the same AuthName but require a different user
  2. Send an ajax request to the page (with the wrong username) (which fails) and then trigger a timeout redirect to the logged out page.

This avoids having a secondary popup in the logout folder requesting another username (which would confuse users). My article uses Jquery but it should be possible to avoid this.

Antony
  • 3,875
  • 30
  • 32
0

While the others are correct in saying that its impossible to logout from basic http authentication there are ways to implement authentication which behave similarly. One obvious appeoach is to use auth_memcookie. If you really want to implement Basic HTTP authentication (i.e. use the browser dialogs for logging in trather than an HTTP form) using this - just set the authentication to a seperate .htaccess protected directory containing a PHP script which redirects back where te user came after createing the memcache session.

symcbean
  • 47,736
  • 6
  • 59
  • 94
0

I achieved this with sending a 401 header. This works fine. With PHP

header('HTTP/1.0 401 Unauthorized');
Imran
  • 440
  • 4
  • 15