346

Is it possible to log out user from a web site if he is using basic authentication?

Killing session is not enough, since, once user is authenticated, each request contains login info, so user is automatically logged in next time he/she access the site using the same credentials.

The only solution so far is to close browser, but that's not acceptable from the usability standpoint.

KyleMit
  • 30,350
  • 66
  • 462
  • 664
Marko
  • 30,263
  • 18
  • 74
  • 108
  • 2
    Just curious. Why do you want to do this? – DOK Oct 24 '08 at 13:41
  • 42
    To be able to log in as a different user. – Marko Oct 24 '08 at 14:08
  • 24
    @DOK - It's a standard social-hacking thing: users should be able to log out while leaving their browser open. Suppose one of your users accesses the site on a public machine? They need to log-off explicitly so that the next user can't access the site as them. – Keith Jun 09 '11 at 07:53
  • 2
    @DOK There is also the problem that it makes it impossible for the user to logout of the site. The server can clear the authorization cookie, and even the session cookie. But when the browser goes to load the `/` page, they will automatically be logged in again. – Ian Boyd May 11 '16 at 19:22
  • I using the method that send a fake request to logout, but it locks the user in customer since there is a strick limitation that 3 times login failed in AD. So, suggest using this method(send a fake request) with caution. – Qianchao Pan Jul 31 '17 at 03:32
  • 1
    If browser shows the login form then the browser itself must provide a button Logout somewhere on toolbar. We should file a feature request to all browsers – Sergey Ponomarev Sep 09 '21 at 12:50
  • There is no concept of "logged in" when using Basic authentication. – OrangeDog Feb 23 '23 at 10:09

26 Answers26

254

Have the user click on a link to https://log:out@example.com/. That will overwrite existing credentials with invalid ones; logging them out.

This does so by sending new credentials in the URL. In this case user="log" password="out".

Brent K.
  • 927
  • 1
  • 11
  • 16
Matthew Welborn
  • 2,581
  • 1
  • 11
  • 2
  • 22
    Why does this one not get more upvotes? Seems like a simple and working solution to me. Are there any known problems with this approach? – amoebe Jan 17 '14 at 14:00
  • 1
    If a device is accessed this way and has a limited number of connections, will this method free up a connection? – Joel Feb 15 '14 at 01:05
  • 1
    @Joel - presumably you can do what you like on the server side in reponse to this. – jwg Feb 18 '14 at 16:16
  • 47
    This would no longer work in Chrome, which for security reasons ignores credentials in a URL. – Thom Jun 06 '14 at 09:49
  • 5
    This Worked for me :) I am using Chrome Version 32.0.1700.102 – abottoni Aug 06 '14 at 13:28
  • 1
    @Thom Seems to work in Chrome by simply typing in the credentials in the correct place in the URL bar's address. Not tried it using a hyperlink. – Geeb Oct 29 '14 at 15:58
  • 1
    @Geeb fairly sure a longstanding bug has recently been fixed in Chrome for this, because I no longer get the issue on sites where it was previously infuriating. – Thom Oct 30 '14 at 12:23
  • 8
    problem: using version 39.0 of chrome, When I click the logout link via this method, Chrome remembers the bad login credentials, and prompts for new login credentials on every page load, until I go to https://example.com without any specified login credentials, to clear chrome's memory. – Scott Jan 23 '15 at 04:08
  • 1
    I am having the same issue as @Scott. system PAUSE's suggestion seems to work just fine. Going with that. – joonas.fi May 14 '15 at 08:30
  • Good answer! Worked for me in both chrome and firefox (44.0.2403.89 and 39.0 respectively). Interestingly, I only changed the password to fake initially, and left the username as was. Chrome did what you'd hope/expect, but firefox just kept you authenticated. Changing both user and password in the link worked in firefox though – herdingofthecats Jul 24 '15 at 11:33
  • 5
    Hi, I cannot use it for https on Chrome. – thienkhoi tran Aug 28 '15 at 09:51
  • Adding fake username and password works for me in Chrome 52.0.2743.116 and FireFox 48.0. IE11 does not support the URL format. – Robban1980 Aug 18 '16 at 01:02
  • 3
    Works well in ff and chrome. Just note that in Chrome you must use the precise URL that the user was browsing at. If you change the URL (even to a different one inside the protected web folder), it will just remember the good credentials. – CpnCrunch Oct 26 '16 at 17:38
  • Also note that this is deprecated in chrome, and will be removed in chrome 57. – CpnCrunch Mar 24 '17 at 21:57
  • As an update to my previous comment, it appears that XHR requests are permitted in chrome, but otherwise chrome will block embedded credentials in urls. https://bugs.chromium.org/p/chromium/issues/detail?id=435547 – CpnCrunch May 25 '17 at 17:47
  • 1
    I can confirm that this does work in Chrome using HTTPS as of 2017-08-17. Chrome Version 58.0.3029.81 (64-bit). – Karl Wilbur Aug 17 '17 at 05:51
  • 1
    Good solution, but the password (`:out`) part is unnecessary, and the login doesn't have to be invalid. – Skippy le Grand Gourou Sep 12 '18 at 09:55
  • Works in FF 69.0.1 – Rebeccah Nov 14 '19 at 23:49
  • Works in FF 69.0.1. Actually, https://invalid@MyWiFiExtenderIPAddress works fine, but https://validusername@MyWiFiExtenderIPAddress still logs me in. – Rebeccah Nov 15 '19 at 00:00
  • Invalidation through URL worked on Chrome Version 90.0.4430.212. – Rafael May 30 '21 at 18:33
  • 2
    I think this answer absolutely needs some explanation! *Why* and *how* are the credentials *overwritten*? – U. Windl Feb 28 '22 at 08:42
  • It is only part of the implementation, we had lot's of trouble that people store a bookmark of the logged out page (In this solution it includes, the logout credentials). This makes login impossible. It is very important, that the server responds with in redirect to some url NOT including the logout password. – gschaden Feb 03 '23 at 14:45
208

An addition to the answer by bobince ...

With Ajax you can have your 'Logout' link/button wired to a Javascript function. Have this function send the XMLHttpRequest with a bad username and password. This should get back a 401. Then set document.location back to the pre-login page. This way, the user will never see the extra login dialog during logout, nor have to remember to put in bad credentials.

system PAUSE
  • 37,082
  • 20
  • 62
  • 59
  • 14
    Good hack, having the user manually enter bad credentials is probably not acceptable for most webapps. – BillMan Mar 28 '11 at 14:08
  • 1
    Just make sure the XMLHttpRequest isn't set to be asynchronous or you may find that the redirection via will take place before the logout request completes. – davidjb Mar 20 '14 at 05:54
  • 5
    You can use the same trick for login as well. That way you can customize the login dialog without having to change the server's authentication method. This article gives some good ideas: [http://www.peej.co.uk/articles/http-auth-with-html-forms.html](http://www.peej.co.uk/articles/http-auth-with-html-forms.html) – Stijn de Witt Apr 01 '14 at 11:12
  • 2
    @davidjb Since synchronous requests are considered deprecated now, an alternative solution might be to redirect the user in the callback of the async request. – Hayden Schiff Jul 31 '15 at 20:20
  • @oxguy3 I follow your hint but without success. It redirects but I'm still logged in – masciugo Jan 23 '16 at 19:01
  • Since AJAX requests encode credentials in URLs, this hack will soon (around June 2017) stop working in Chrome. Chrome will, for security reasons, [ignore credentials encoded in URLs.](https://www.chromestatus.com/feature/5669008342777856) – David Apr 14 '17 at 10:00
  • 1
    David: chrome now permits this for XHRs, and I can confirm that it is still working in chrome canary. https://bugs.chromium.org/p/chromium/issues/detail?id=435547 – CpnCrunch May 25 '17 at 17:49
188

Basic Authentication wasn't designed to manage logging out. You can do it, but not completely automatically.

What you have to do is have the user click a logout link, and send a ‘401 Unauthorized’ in response, using the same realm and at the same URL folder level as the normal 401 you send requesting a login.

They must be directed to input wrong credentials next, eg. a blank username-and-password, and in response you send back a “You have successfully logged out” page. The wrong/blank credentials will then overwrite the previous correct credentials.

In short, the logout script inverts the logic of the login script, only returning the success page if the user isn't passing the right credentials.

The question is whether the somewhat curious “don't enter your password” password box will meet user acceptance. Password managers that try to auto-fill the password can also get in the way here.

Edit to add in response to comment: re-log-in is a slightly different problem (unless you require a two-step logout/login obviously). You have to reject (401) the first attempt to access the relogin link, than accept the second (which presumably has a different username/password). There are a few ways you could do this. One would be to include the current username in the logout link (eg. /relogin?username), and reject when the credentials match the username.

bobince
  • 528,062
  • 107
  • 651
  • 834
  • 2
    I'll try this approach. The point of logout (in this case) is to enable user to log in as different user, so it is perfectly acceptable solution. As for auto-fill password, it is up to user if he will use it or not. Thanks – Marko Oct 24 '08 at 14:07
  • Is this still the only way? I've done an ASP.Net MVC and jQuery implementation that works, but I'm still not happy with it: http://stackoverflow.com/questions/6277919 – Keith Jun 09 '11 at 07:51
  • @Keith: Still only this and systemPAUSE's answer (which doesn't work on all browsers, but is smoother than the manual approach when it does work). – bobince Jun 09 '11 at 21:49
  • Brilliant solution. I used a combo of your answer and this post http://stackoverflow.com/questions/5957822/how-to-clear-basic-authentication-details-in-chrome – Praesagus Nov 27 '13 at 22:42
  • 21
    The W3C is so active on the HTML spec. But the HTTP spec is languishing. W3C should have fixed this problem about two decades ago. With the rise in use of REST services, a robust native authentication method is need of the day. – Dojo Dec 01 '14 at 13:38
  • 11
    This doesn't appear to work properly in Chrome 46 browsing on localhost. Chrome appears to keep both the old (correct) password and the new password which you specify. After navigating to the logout page, chrome correctly uses the new password UNTIL IT ENCOUNTERS A 401 UNAUTHORIZED ON A PAGE ON YOUR SITE. After the first 401, Chrome reverts back to the old (correct) password. So it really didn't delete the password in the first place it seems. – vancan1ty Jan 08 '16 at 22:10
  • Can confirm the @vancan1ty issue still happens on Chrome 77.0.3865.75 , always when browsing localhost (401 on other origins seems to correctly delete the auth cache). – Nahuel Greco Sep 15 '19 at 01:26
81

You can do it entirely in JavaScript:

IE has (for a long time) standard API for clearing Basic Authentication cache:

document.execCommand("ClearAuthenticationCache")

Should return true when it works. Returns either false, undefined or blows up on other browsers.

New browsers (as of Dec 2012: Chrome, FireFox, Safari) have "magic" behavior. If they see a successful basic auth request with any bogus other username (let's say logout) they clear the credentials cache and possibly set it for that new bogus user name, which you need to make sure is not a valid user name for viewing content.

Basic example of that is:

var p = window.location.protocol + '//'
// current location must return 200 OK for this GET
window.location = window.location.href.replace(p, p + 'logout:password@')

An "asynchronous" way of doing the above is to do an AJAX call utilizing the logout username. Example:

(function(safeLocation){
    var outcome, u, m = "You should be logged out now.";
    // IE has a simple solution for it - API:
    try { outcome = document.execCommand("ClearAuthenticationCache") }catch(e){}
    // Other browsers need a larger solution - AJAX call with special user name - 'logout'.
    if (!outcome) {
        // Let's create an xmlhttp object
        outcome = (function(x){
            if (x) {
                // the reason we use "random" value for password is 
                // that browsers cache requests. changing
                // password effectively behaves like cache-busing.
                x.open("HEAD", safeLocation || location.href, true, "logout", (new Date()).getTime().toString())
                x.send("")
                // x.abort()
                return 1 // this is **speculative** "We are done." 
            } else {
                return
            }
        })(window.XMLHttpRequest ? new window.XMLHttpRequest() : ( window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : u ))
    }
    if (!outcome) {
        m = "Your browser is too old or too weird to support log out functionality. Close all windows and restart the browser."
    }
    alert(m)
    // return !!outcome
})(/*if present URI does not return 200 OK for GET, set some other 200 OK location here*/)

You can make it a bookmarklet too:

javascript:(function (c) {
  var a, b = "You should be logged out now.";
  try {
    a = document.execCommand("ClearAuthenticationCache")
  } catch (d) {
  }
  a || ((a = window.XMLHttpRequest ? new window.XMLHttpRequest : window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : void 0) ? (a.open("HEAD", c || location.href, !0, "logout", (new Date).getTime().toString()), a.send(""), a = 1) : a = void 0);
  a || (b = "Your browser is too old or too weird to support log out functionality. Close all windows and restart the browser.");
  alert(b)
})(/*pass safeLocation here if you need*/);
Carson
  • 6,105
  • 2
  • 37
  • 45
ddotsenko
  • 4,926
  • 25
  • 24
  • 1
    Does this require special server-side handling of the `logout` username and/or logout URL? – ulidtko Oct 07 '13 at 19:30
  • 1
    @ulidtko No, it shouldn't - all handling is client-side. The only situation that would need special handling is if a user called ``logout`` happens to exist and happens to have the generated password. In that almost impossibly-rare case, change the user ID to one that won't exist in your system. – davidjb Mar 20 '14 at 05:58
  • 3
    I used the bookmarklet above today and I works well. – David Gleba May 13 '15 at 12:51
  • I used this and it worked for Chrome and FF. I only had to do an extra "GET" on my logout.php page to clear the $_SESSION. – urban Oct 09 '15 at 10:08
  • 2
    The bookmarklet works on Edge, too. Simply use with `Logout` – Eric Nov 30 '15 at 06:57
  • I'm trying to implement this and it doesn't seem to work - even using the bookmarklet. I get the message saying I should be logged out now, but I'm still logged in. Has Chrome in 2017 prevented this "hack" from working? – Codemonkey Nov 06 '17 at 14:47
  • It is still working in Chrome 80, I use the function(x)...XMLHttpRequest part only. Just remember to load the safeLocation at the end when done. – Tomofumi Mar 10 '20 at 03:43
  • works. don't use fetch, it doesn't allow creds. I added a `window.location.reload()` after to force browser to immediately reprompt user for new login and did synchronous XHR request. – lazieburd Aug 15 '20 at 15:17
  • 1
    at least with localhost, https and chrome 87, this just says "You should be logged out now" but you're still fully logged in. – eis Dec 23 '20 at 08:28
26

The following function is confirmed working for Firefox 40, Chrome 44, Opera 31 and IE 11.
Bowser is used for browser detection, jQuery is also used.

- secUrl is the url to a password protected area from which to log out.
- redirUrl is the url to a non password protected area (logout success page).
- you might wish to increase the redirect timer (currently 200ms).

function logout(secUrl, redirUrl) {
    if (bowser.msie) {
        document.execCommand('ClearAuthenticationCache', 'false');
    } else if (bowser.gecko) {
        $.ajax({
            async: false,
            url: secUrl,
            type: 'GET',
            username: 'logout'
        });
    } else if (bowser.webkit) {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", secUrl, true);
        xmlhttp.setRequestHeader("Authorization", "Basic logout");
        xmlhttp.send();
    } else {
        alert("Logging out automatically is unsupported for " + bowser.name
            + "\nYou must close the browser to log out.");
    }
    setTimeout(function () {
        window.location.href = redirUrl;
    }, 200);
}
mthoring
  • 261
  • 3
  • 2
  • this is the most comprehensive answer – belidzs Oct 21 '15 at 14:51
  • Is there any reason for the `$.ajax` variant being synchronous (`async: false`) and the `xmlhttp` variant being asynchronous (the `true` in `open()`)? – Bowi Sep 14 '17 at 13:34
  • 1
    Chrome now uses the rendering engine Blink, so you have to change `(bowser.gecko)` to `(bowser.gecko || bowser.blink)`. – Bowi Sep 15 '17 at 14:10
  • 1
    Why does gecko/blink use `$.ajax` and webkit use `new XMLHttpRequest`? Shouldn't gecko/blink be able to do `XMLHttpRequest` and webkit be able to do `$.ajax` too? I'm confused. – RemyNL Oct 31 '18 at 12:13
14

Here's a very simple Javascript example using jQuery:

function logout(to_url) {
    var out = window.location.href.replace(/:\/\//, '://log:out@');

    jQuery.get(out).error(function() {
        window.location = to_url;
    });
}

This log user out without showing him the browser log-in box again, then redirect him to a logged out page

Romuald Brunet
  • 5,595
  • 4
  • 38
  • 34
11

This isn't directly possible with Basic-Authentication.

There's no mechanism in the HTTP specification for the server to tell the browser to stop sending the credentials that the user already presented.

There are "hacks" (see other answers) typically involving using XMLHttpRequest to send an HTTP request with incorrect credentials to overwrite the ones originally supplied.

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

It's actually pretty simple.

Just visit the following in your browser and use wrong credentials: http://username:password@yourdomain.com

That should "log you out".

Chiedo
  • 7,288
  • 3
  • 26
  • 22
9

Just for the record, there is a new HTTP Response Header called Clear-Site-Data. If your server reply includes a Clear-Site-Data: "cookies" header, then the authentication credentials (not only cookies) should be removed. I tested it on Chrome 77 but this warning shows on the console:

Clear-Site-Data header on 'https://localhost:9443/clear': Cleared data types:
"cookies". Clearing channel IDs and HTTP authentication cache is currently not
supported, as it breaks active network connections.

And the auth credentials aren't removed, so this doesn't works (for now) to implement basic auth logouts, but maybe in the future will. Didn't test on other browsers.

References:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data

https://www.w3.org/TR/clear-site-data/

https://github.com/w3c/webappsec-clear-site-data

https://caniuse.com/#feat=mdn-http_headers_clear-site-data_cookies

Nahuel Greco
  • 1,080
  • 14
  • 12
  • https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data - looks like Safari doesn't like this – James Stevens Nov 12 '21 at 16:52
  • Tested on Firefox 106, same behavior. When setting `Clear-Site-Data: "cookies"` (which should also clear authentication data according to MDN), the console says `Clear-Site-Data header forced the clean up of “cookies” data`, but the `Authentication` header persists on following requests – Robin Métral Dec 01 '22 at 21:39
6

This is working for IE/Netscape/Chrome :

      function ClearAuthentication(LogOffPage) 
  {
     var IsInternetExplorer = false;    

     try
     {
         var agt=navigator.userAgent.toLowerCase();
         if (agt.indexOf("msie") != -1) { IsInternetExplorer = true; }
     }
     catch(e)
     {
         IsInternetExplorer = false;    
     };

     if (IsInternetExplorer) 
     {
        // Logoff Internet Explorer
        document.execCommand("ClearAuthenticationCache");
        window.location = LogOffPage;
     }
     else 
     {
        // Logoff every other browsers
    $.ajax({
         username: 'unknown',
         password: 'WrongPassword',
             url: './cgi-bin/PrimoCgi',
         type: 'GET',
         beforeSend: function(xhr)
                 {
            xhr.setRequestHeader("Authorization", "Basic AAAAAAAAAAAAAAAAAAA=");
         },

                 error: function(err)
                 {
                    window.location = LogOffPage;
             }
    });
     }
  }


  $(document).ready(function () 
  {
      $('#Btn1').click(function () 
      {
         // Call Clear Authentication 
         ClearAuthentication("force_logout.html"); 
      });
  });          
Claudio
  • 61
  • 1
  • 1
5

All you need is redirect user on some logout URL and return 401 Unauthorized error on it. On error page (which must be accessible without basic auth) you need to provide a full link to your home page (including scheme and hostname). User will click this link and browser will ask for credentials again.

Example for Nginx:

location /logout {
    return 401;
}

error_page 401 /errors/401.html;

location /errors {
    auth_basic off;
    ssi        on;
    ssi_types  text/html;
    alias /home/user/errors;
}

Error page /home/user/errors/401.html:

<!DOCTYPE html>
<p>You're not authorised. <a href="<!--# echo var="scheme" -->://<!--# echo var="host" -->/">Login</a>.</p>
Envek
  • 4,426
  • 3
  • 34
  • 42
  • 1
    I would further suggest using `http_host` in `401.html` instead of simply `host`, as the former also adds the port number (in case a non-standard port is being used) – Emil Koutanov Jun 30 '19 at 12:13
5

I've just tested the following in Chrome (79), Firefox (71) and Edge (44) and it works fine. It applies the script solution as others noted above.

Just add a "Logout" link and when clicked return the following html

    <div>You have been logged out. Redirecting to home...</div>    

<script>
    var XHR = new XMLHttpRequest();
    XHR.open("GET", "/Home/MyProtectedPage", true, "no user", "no password");
    XHR.send();

    setTimeout(function () {
        window.location.href = "/";
    }, 3000);
</script>
Teo Bebekis
  • 625
  • 8
  • 9
  • What do you mean by "when clicked, return the following html"? Do you mean set the onclick function of the button to the javascript in the script? – Michael Jan 27 '21 at 18:23
  • @Michael no, just an anchor element, e.g. Logout – Teo Bebekis Feb 03 '21 at 21:10
  • 1
    This works fine for logout on Chrome 97 and Firefox 96, but Firefox will remember the "no user" and won't prompt for another login when you try to login again. – Gene Vincent Jan 25 '22 at 09:36
4

add this to your application :

@app.route('/logout')
def logout():
    return ('Logout', 401, {'WWW-Authenticate': 'Basic realm="Login required"'})
Amir Mofakhar
  • 6,843
  • 2
  • 14
  • 5
2
function logout() {
  var userAgent = navigator.userAgent.toLowerCase();

  if (userAgent.indexOf("msie") != -1) {
    document.execCommand("ClearAuthenticationCache", false);
  }

  xhr_objectCarte = null;

  if(window.XMLHttpRequest)
    xhr_object = new XMLHttpRequest();
  else if(window.ActiveXObject)
    xhr_object = new ActiveXObject("Microsoft.XMLHTTP");
  else
    alert ("Your browser doesn't support XMLHTTPREQUEST");

  xhr_object.open ('GET', 'http://yourserver.com/rep/index.php', false, 'username', 'password');
  xhr_object.send ("");
  xhr_object = null;

  document.location = 'http://yourserver.com'; 
  return false;
}
Charlie
  • 21
  • 2
2
 function logout(url){
    var str = url.replace("http://", "http://" + new Date().getTime() + "@");
    var xmlhttp;
    if (window.XMLHttpRequest) xmlhttp=new XMLHttpRequest();
    else xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    xmlhttp.onreadystatechange=function()
    {
        if (xmlhttp.readyState==4) location.reload();
    }
    xmlhttp.open("GET",str,true);
    xmlhttp.setRequestHeader("Authorization","Basic xxxxxxxxxx")
    xmlhttp.send();
    return false;
}
2

Based on what I read above I got a simple solution that works on any browser:

1) on you logout page you call an ajax to your login back end. Your login back end must accept logout user. Once the back end accept, the browser clear the current user and assumes the "logout" user.

$.ajax({
    async: false,
    url: 'http://your_login_backend',
    type: 'GET',
    username: 'logout'
});      

setTimeout(function () {
    window.location.href = 'http://normal_index';
}, 200);

2) Now when the user got back to the normal index file it will try to automatic enter in the system with the user "logout", on this second time you must block it by reply with 401 to invoke the login/password dialog.

3) There are many ways to do that, I created two login back ends, one that accepts the logout user and one that doesn't. My normal login page use the one that doesn't accept, my logout page use the one that accepts it.

Foad
  • 390
  • 3
  • 6
2

Sending https://invalid_login@hostname works fine everywhere except Safari on Mac (well, not checked Edge but should work there too).

Logout doesn't work in Safari when a user selects 'remember password' in the HTTP Basic Authentication popup. In this case the password is stored in Keychain Access (Finder > Applications > Utilities > Keychain Access (or CMD+SPACE and type "Keychain Access")). Sending https://invalid_login@hostname doesn't affect Keychain Access, so with this checkbox it is not possible to logout on Safari on Mac. At least it is how it works for me.

MacOS Mojave (10.14.6), Safari 12.1.2.

The code below works fine for me in Firefox (73), Chrome (80) and Safari (12). When a user navigates to a logout page the code is executed and drops the credentials.

    //It should return 401, necessary for Safari only
    const logoutUrl = 'https://example.com/logout'; 
    const xmlHttp = new XMLHttpRequest();
    xmlHttp.open('POST', logoutUrl, true, 'logout');
    xmlHttp.send();

Also for some reason Safari doesn't save credentials in the HTTP Basic Authentication popup even when the 'remember password' is selected. The other browsers do this correctly.

Fogus
  • 366
  • 1
  • 2
  • 13
2

This is how my logout is working using form:

  1. create basic auth user logout with password logout
  2. create folder logout/ and add .htaccess: with line 'require user logout'
RewriteEngine On
AuthType Basic
AuthName "Login"
AuthUserFile /mypath/.htpasswd
require user logout
  1. add logout button to website as form like:
<form action="https://logout:logout@example.com/logout/" method="post">
   <button type="submit">Logout</button>
</form>
  1. logout/index.php could be something like:
<?php
echo "LOGOUT SUCCESS";
header( "refresh:2; url=https://example.com" );
?>

5.9.2022 confirmed working on chrome, edge and samsung android internet browser

JPalo
  • 41
  • 3
1

This JavaScript must be working for all latest version browsers:

//Detect Browser
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
    // Opera 8.0+ (UA detection to detect Blink/v8-powered Opera)
var isFirefox = typeof InstallTrigger !== 'undefined';   // Firefox 1.0+
var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
    // At least Safari 3+: "[object HTMLElementConstructor]"
var isChrome = !!window.chrome && !isOpera;              // Chrome 1+
var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
var Host = window.location.host;


//Clear Basic Realm Authentication
if(isIE){
//IE
    document.execCommand("ClearAuthenticationCache");
    window.location = '/';
}
else if(isSafari)
{//Safari. but this works mostly on all browser except chrome
    (function(safeLocation){
        var outcome, u, m = "You should be logged out now.";
        // IE has a simple solution for it - API:
        try { outcome = document.execCommand("ClearAuthenticationCache") }catch(e){}
        // Other browsers need a larger solution - AJAX call with special user name - 'logout'.
        if (!outcome) {
            // Let's create an xmlhttp object
            outcome = (function(x){
                if (x) {
                    // the reason we use "random" value for password is 
                    // that browsers cache requests. changing
                    // password effectively behaves like cache-busing.
                    x.open("HEAD", safeLocation || location.href, true, "logout", (new Date()).getTime().toString())
                    x.send("");
                    // x.abort()
                    return 1 // this is **speculative** "We are done." 
                } else {
                    return
                }
            })(window.XMLHttpRequest ? new window.XMLHttpRequest() : ( window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : u )) 
        }
        if (!outcome) {
            m = "Your browser is too old or too weird to support log out functionality. Close all windows and restart the browser."
        }
        alert(m);
        window.location = '/';
        // return !!outcome
    })(/*if present URI does not return 200 OK for GET, set some other 200 OK location here*/)
}
else{
//Firefox,Chrome
    window.location = 'http://log:out@'+Host+'/';
}
Amit Shah
  • 11
  • 1
1

type chrome://restart in the address bar and chrome, with all its apps that are running in background, will restart and the Auth password cache will be cleaned.

Ali
  • 6,808
  • 3
  • 37
  • 47
0
  • use a session ID (cookie)
  • invalidate the session ID on the server
  • Don't accept users with invalid session IDs
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • It's also good to offer Basic Authentication as a backup login scheme for when cookies aren't available. – bobince Oct 24 '08 at 13:35
0

I updated mthoring's solution for modern Chrome versions:

function logout(secUrl, redirUrl) {
    if (bowser.msie) {
        document.execCommand('ClearAuthenticationCache', 'false');
    } else if (bowser.gecko) {
        $.ajax({
            async: false,
            url: secUrl,
            type: 'GET',
            username: 'logout'
        });
    } else if (bowser.webkit || bowser.chrome) {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open(\"GET\", secUrl, true);
        xmlhttp.setRequestHeader(\"Authorization\", \"Basic logout\");\
        xmlhttp.send();
    } else {
// http://stackoverflow.com/questions/5957822/how-to-clear-basic-authentication-details-in-chrome
        redirUrl = url.replace('http://', 'http://' + new Date().getTime() + '@');
    }
    setTimeout(function () {
        window.location.href = redirUrl;
    }, 200);
}
Max
  • 1,135
  • 13
  • 15
0

As others have said, we need to get the same URL and send an error (e.g., 401: StatusUnauthorized something like that), and that's it.

And I use the Get method to let it know I need to logout,

Here is a full example of writing with golang.

package main

import (
    "crypto/subtle"
    "fmt"
    "log"
    "net/http"
)

func BasicAuth(username, password, realm string, handlerFunc http.HandlerFunc) http.HandlerFunc {

    return func(w http.ResponseWriter, r *http.Request) {
        queryMap := r.URL.Query()
        if _, ok := queryMap["logout"]; ok { // localhost:8080/public/?logout
            w.WriteHeader(http.StatusUnauthorized) // 401
            _, _ = w.Write([]byte("Success logout!\n"))
            return
        }

        user, pass, ok := r.BasicAuth()

        if !ok ||
            subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 ||
            subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate
            w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`", charset="UTF-8"`)
            w.WriteHeader(http.StatusUnauthorized)
            _, _ = w.Write([]byte("Unauthorised.\n"))
            return
        }

        handlerFunc(w, r)
    }
}

type UserInfo struct {
    name string
    psw  string
}

func main() {

    portNumber := "8080"
    guest := UserInfo{"guest", "123"}

    // localhost:8080/public/  -> ./public/everyone
    publicHandler := http.StripPrefix(
        "/public/", http.FileServer(http.Dir("./public/everyone")),
    )

    publicHandlerFunc := func(w http.ResponseWriter, r *http.Request) {
        switch r.Method {
        case http.MethodGet:
            publicHandler.ServeHTTP(w, r)
        /*
            case http.MethodPost:
            case http.MethodPut:
            case http.MethodDelete:
        */
        default:
            return
        }
    }

    http.HandleFunc("/public/",
        BasicAuth(guest.name, guest.psw, "Please enter your username and password for this site",
            publicHandlerFunc),
    )

    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", portNumber), nil))
}

When you have already logout, then you need to refresh (F5) the page. Otherwise, you may see the old content.

Carson
  • 6,105
  • 2
  • 37
  • 45
0

Actually I think basic authentication was intended to be used with static pages, not for any sophisticated session management or CGI pages.

Thus when wanting session management you should design a classic "login form" to query for user and password (maybe 2nd factor as well). The CGI form handler should convert successful authentication to a session (ID) that is remembered on the server and (in a cookie or as part of the URI).

Then logout can be implemented simply by making the server (and client) "forget" the session. The other advantage is that (even when encrypted) the user and password is not send with every request to the server (instead the session ID would be sent).

If the session ID on the server is combined with a timestamp for the "last action" performed, then session timeout could be implemented by comparing that timestamp with the current time: If the time span is too large, "timeout" the session by forgetting the session ID.

Any request to an invalid session would cause a redirection to the login page (or maybe if you want to make it more comfortable, you can have a "revalidation form" that requests the password again, too).

As a proof of concept I had implemented a completely cookie-free session management that is purely URI-based (the session ID is always part of the URI). However the complete code would be too long for this answer.

Special care about performance has to be taken when wanting to handle several thousands of concurrent sessions.

U. Windl
  • 3,480
  • 26
  • 54
0

For anyone who use Windows Authentication (also known as Negotiate, Kerberos, or NTLM authentication), I use ASP.NET Core with Angular.

I found an efficient manner to change users !

I modify my login method on the javascript side like that :

protected login(changeUser: boolean = false): Observable<AuthInfo> {
  let params = new HttpParams();
  if(changeUser) {
    let dateNow = this.datePipe.transform(new Date(), 'yyyy-MM-dd HH:mm:ss');
    params = params.set('changeUser', dateNow!);
  }
  const url: string = `${environment.yourAppsApiUrl}/Auth/login`;
  return this.http.get<AuthInfo>(url, { params: params });
}

Here is my method on the backend :

[Route("api/[controller]")]
[ApiController]
[Produces("application/json")]
[Authorize(AuthenticationSchemes = NegotiateDefaults.AuthenticationScheme)]
public class AuthController : Controller
{
  [HttpGet("login")]
  public async Task<IActionResult> Login(DateTime? changeUser = null)
  {
      if (changeUser > DateTime.Now.AddSeconds(-3))
          return Unauthorized();

      ...
      ... (login process)
      ...

      return Ok(await _authService.GetToken());
    }
}

return Unauthorized() return the 401 code that causes the browser identification popup window to appear, here is the process : enter image description here

  • I transmit the date now as a parameter if I want to change user.
  • I return the 401 code if no more than 3 seconds have passed since that moment Now.
  • I complete my credential and the same request with the same parameter is sent to the backend.
  • Since more than 3 seconds have passed, I continue the login process but this time with the new credential !
A. Morel
  • 9,210
  • 4
  • 56
  • 45
-1
    function logout(secUrl, redirUrl) {
        if (bowser.msie) {
            document.execCommand('ClearAuthenticationCache', 'false');
        } else if (bowser.gecko) {
            $.ajax({
                async: false,
                url: secUrl,
                type: 'GET',
                username: 'logout'
            });
        } else if (bowser.webkit) {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.open("GET", secUrl, true);
            xmlhttp.setRequestHeader("Authorization", "Basic logout");
            xmlhttp.send();
        } else {
            alert("Logging out automatically is unsupported for " + bowser.name
                + "\nYou must close the browser to log out.");
        }
        setTimeout(function () {
            window.location.href = redirUrl;
        }, 200);
    }

I tried using the above in the following way.

?php
    ob_start();
    session_start();
    require_once 'dbconnect.php';

    // if session is not set this will redirect to login page
    if( !isset($_SESSION['user']) ) {
        header("Location: index.php");
        exit;
    }
    // select loggedin users detail
    $res=mysql_query("SELECT * FROM users WHERE userId=".$_SESSION['user']);
    $userRow=mysql_fetch_array($res);
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Welcome - <?php echo $userRow['userEmail']; ?></title>
<link rel="stylesheet" href="assets/css/bootstrap.min.css" type="text/css"  />
<link rel="stylesheet" href="style.css" type="text/css" />

    <script src="assets/js/bowser.min.js"></script>
<script>
//function logout(secUrl, redirUrl)
//bowser = require('bowser');
function logout(secUrl, redirUrl) {
alert(redirUrl);
    if (bowser.msie) {
        document.execCommand('ClearAuthenticationCache', 'false');
    } else if (bowser.gecko) {
        $.ajax({
            async: false,
            url: secUrl,
            type: 'GET',
            username: 'logout'
        });
    } else if (bowser.webkit) {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", secUrl, true);
        xmlhttp.setRequestHeader("Authorization", "Basic logout");
        xmlhttp.send();
    } else {
        alert("Logging out automatically is unsupported for " + bowser.name
            + "\nYou must close the browser to log out.");
    }
    window.location.assign(redirUrl);
    /*setTimeout(function () {
        window.location.href = redirUrl;
    }, 200);*/
}


function f1()
    {
       alert("f1 called");
       //form validation that recalls the page showing with supplied inputs.    
    }
</script>
</head>
<body>

    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="http://www.codingcage.com">Coding Cage</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <ul class="nav navbar-nav">
            <li class="active"><a href="http://www.codingcage.com/2015/01/user-registration-and-login-script-using-php-mysql.html">Back to Article</a></li>
            <li><a href="http://www.codingcage.com/search/label/jQuery">jQuery</a></li>
            <li><a href="http://www.codingcage.com/search/label/PHP">PHP</a></li>
          </ul>
          <ul class="nav navbar-nav navbar-right">

            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
              <span class="glyphicon glyphicon-user"></span>&nbsp;Hi' <?php echo $userRow['userEmail']; ?>&nbsp;<span class="caret"></span></a>
              <ul class="dropdown-menu">
                <li><a href="logout.php?logout"><span class="glyphicon glyphicon-log-out"></span>&nbsp;Sign Out</a></li>
              </ul>
            </li>
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav> 

    <div id="wrapper">

    <div class="container">

        <div class="page-header">
        <h3>Coding Cage - Programming Blog</h3>
        </div>

        <div class="row">
        <div class="col-lg-12" id="div_logout">
        <h1 onclick="logout(window.location.href, 'www.espncricinfo.com')">MichaelA1S1! Click here to see log out functionality upon click inside div</h1>
        </div>
        </div>

    </div>

    </div>

    <script src="assets/jquery-1.11.3-jquery.min.js"></script>
    <script src="assets/js/bootstrap.min.js"></script>


</body>
</html>
<?php ob_end_flush(); ?>

But it only redirects you to new location. No logout.

jwg
  • 5,547
  • 3
  • 43
  • 57