24

I want users to be able to log in via HTTP Basic authentication modes.

The problem is that I also want them to be able to log out again - weirdly browsers just don't seem to support that.

This is considered to be a social-hacking risk - user leaves their machine unlocked and their browser open and someone else can easily visit the site as them. Note that just closing the browser-tab is not enough to reset the token, so it could be an easy thing for users to miss.

So I've come up with a workaround, but it's a total cludge:

1) Redirect them to a Logoff page

2) On that page fire a script to ajax load another page with dummy credentials:

$j.ajax({
    url: '<%:Url.Action("LogOff401", new { id = random })%>',
    type: 'POST',
    username: '<%:random%>',
    password: '<%:random%>',
    success: function () { alert('logged off'); }
});

3) That should always return 401 the first time (to force the new credentials to be passed) and then only accept the dummy credentials:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOff401(string id)
{
    // if we've been passed HTTP authorisation
    string httpAuth = this.Request.Headers["Authorization"];
    if (!string.IsNullOrEmpty(httpAuth) &&
        httpAuth.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
    {
        // build the string we expect - don't allow regular users to pass
        byte[] enc = Encoding.UTF8.GetBytes(id + ':' + id);
        string expected = "basic " + Convert.ToBase64String(enc);

        if (string.Equals(httpAuth, expected, StringComparison.OrdinalIgnoreCase))
        {
            return Content("You are logged out.");
        }
    }

    // return a request for an HTTP basic auth token, this will cause XmlHttp to pass the new header
    this.Response.StatusCode = 401; 
    this.Response.StatusDescription = "Unauthorized";
    this.Response.AppendHeader("WWW-Authenticate", "basic realm=\"My Realm\""); 

    return Content("Force AJAX component to sent header");
}

4) Now the random string credentials have been accepted and cached by the browser instead. When they visit another page it will try to use them, fail, and then prompt for the right ones.

Note that my code examples are using jQuery and ASP.Net MVC, but the same thing should be possible with any technology stack.

There's another way to do this in IE6 and above:

document.execCommand("ClearAuthenticationCache");

However that clears all authentication - they log out of my site and they're logged out of their e-mail too. So that's out.

Is there any better way to do this?

I've seen other questions on this, but they're 2 years old - is there any better way now in IE9, FX4, Chrome etc?

If there is no better way to do this can this cludge be relied upon? Is there any way to make it more robust?

Community
  • 1
  • 1
Keith
  • 150,284
  • 78
  • 298
  • 434
  • 2
    HTTP Authentication has no concept of logging out. What you should be doing instead is creating an HTTP session on the server side when the user logs in, and have the server keep track of that session, such as with a client-side cookie or WebStorage. To logout, you simply end the HTTP session and kill the cookie/storage that refers to it. Since HTTP is stateless, and requests to the same server can span across multiple TCP connections, this is how most sites operate when dealing with authentication. – Remy Lebeau Dec 23 '15 at 23:24
  • I think the ugliness of HTTP Authentication in browsers long ago drove pretty much everybody to use their own login page (with, as mentioned above, their own session management). If, for heaven knows what reason in 2016, you want to use HTTP Authentication, simply keep your own session and for anyone whose session is not currently open, return your 401 (no need for all the complexity in the original question above). – John Hascall Feb 03 '16 at 16:21
  • @JohnHascall that's basically just using the session as authentication, which would be fine, but if you don't have an auth cookie you probably don't have a session one and vice versa. There's a whole other argument about whether you _should_ use HTTP basic authentication (you almost certainly shouldn't). Yeah, it's 2016, but plenty of customers are big companies and governments and they move slow - they only give up on an old cheap technology when it's forced from their hands. – Keith Feb 03 '16 at 19:50
  • I think of us as ossified, but we ditched it in 1999-2002, so OMG. – John Hascall Feb 03 '16 at 20:12
  • @Keith Over TLS, Basic http authentication is both right, good, correct, and ideal - especially with `XmlHttpRequest`, and it's built-in method for supplying credentials. Passing a base64 encoded password over TLS is no less secure than passing a password in a `text` input field. Except we have to roll our own form authentication system with the latter. `XmlHttpRequest.open(method, url, Username, Password)` conveniently already exists. – Ian Boyd May 11 '16 at 19:30
  • @IanBoyd I don't know about ideal - basic authentication resends and rechecks the password every time, but to avoid brute force attacks it's usually best to use a deliberately slow password hash. If use a secure, slow password hash then you probably want authentication that switches to a token model once that step is complete so that you don't have that overhead on every request. – Keith May 11 '16 at 19:42
  • @Keith I meant in terms of getting a password to the server in the first place. With it you can use secure password storage systems like scrypt/bcrypt. And, of course, once you're logged in, the user agent can be given an authorization or session cookie. Given the choices of password validation: Basic, Digest, other custom header based version, or a custom forms based version, Basic is the best. – Ian Boyd May 11 '16 at 20:03

1 Answers1

5

The short anser is:
There is no reliable procedure for achieving a "logoff" using HTTP Basic or Digest authentication given current implemenations of basic auth.

Such authentication works by having the client add an Authorization header to the request.
If for a certain resource the server is not satisfied with the credentials provided (e.g. if there are none), it will responde with a "401 Unauthorized" status code and request authentication. For that purpose it will provide a WWW-Authenticate header with the response.

A client need not wait for a server requesting authentication. It may simply provide an Authorization header based on some local assumptions (e.g. cached information from the last successful attempt).

While your outlined approach on "clearing" out authentication info has a good chance of working with a wide range of clients (namely widespread browsers), there is absolutely no guarantee that a nother client might be "smarter" and simply discriminate proper authentication data for your "logout" page and any other pages of the target site.

You will recognize a similar "problem" with using client side certificate based authentication. As long as there is no explicit support from clients you might fight on lost ground.

So, if "logoff" is a concern, move over to any session based authentication.

If you have access to the implementation of authentication on the server side you might be able implementing a functionality that will disregard authentication information presented with Authorization header (if still identical to what has been presented during current "session) on request of your application level code (or provide some "timout" after which any credentials will be re-requested), so that the client will ask the user for providing "new" credentials (performing a new login).

rpy
  • 3,953
  • 2
  • 20
  • 31