27

I have a few internal .net web application here that require users to "log out" of them. I know this may seem moot on an Intranet application, but nonetheless it is there.

We are using Windows authentication for our Intranet apps, so we tie in to our Active Directory with Basic Authentication and the credentials get stored in the browser cache, as opposed to a cookie when using .net forms authentication.

In IE6+ you can leverage a special JavaScript function they created by doing the following:

document.execCommand("ClearAuthenticationCache", "false")

However, for the other browsers that are to be supported (namely Firefox at the moment, but I strive for multi-browser support), I simply display message to the user that they need to close their browser to log out of the application, which effectively flushes the application cache.

Does anybody know of some commands/hacks/etc. that I can use in other browsers to flush the authentication cache?

Chris
  • 6,761
  • 6
  • 52
  • 67
Dillie-O
  • 29,277
  • 14
  • 101
  • 140

7 Answers7

13

I've come up with a fix that seems fairly consistent but is hacky and I'm still not happy with it.

It does work though :-)

1) Redirect them to a Logoff page

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

$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 (sample in MVC):

[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.

Community
  • 1
  • 1
Keith
  • 150,284
  • 78
  • 298
  • 434
  • Hmm, that is a rather interesting approach to things. Can you do it without MVC if I already have WinForms apps in place? – Dillie-O Jun 09 '11 at 18:58
  • @Dillie-O yeah, just create a new `.aspx` or `.ashx` page, stick this in the page load, add `string id = Request["id"];` to the top and replace the two `return Content(...` with `Response.End()`. Alternatively you could create a custom `HttpHandler`. All it need to do is return an HTTP 401 for the valid username & password, and then return an HTTP 200 for the dummy one (which fools the browser into caching that instead). – Keith Jun 09 '11 at 22:14
  • 2
    This works reliably but on FF and WebKit it will bring up a login dialog that might confuse the user. I was hoping that the use of AJAX would prevent that. Still researching. – AnthonyVO Dec 14 '11 at 20:54
  • @AnthonyVO - I wrote this originally on Chrome (webkit based) and it didn't re-prompt, I'm not sure why that's different for you. I've only included a subset of my code though, so it's possible I missed something in my answer. What tech stack are you using? – Keith Dec 15 '11 at 00:28
  • IIS 7.5 plus all browsers. Not MVC WebApp. Regarding your comment to @Dillie-O about using a HttpHandler, that won't work because a HttpHandler won't see the authentication Traffic. See: http://stackoverflow.com/questions/769432/ihttphandler-vs-ihttpmodule. Going to try a iHttpModule.. – AnthonyVO Dec 15 '11 at 21:39
  • Sorry to say that I could not get this working reliably. Better in IIS 7.5 but IIS 6 not. Using a iHttpModule seemed to work best for a WebApp because I could hook into the ".BeginRequest" which was fairly low down. My biggest problem was that IIS or something kept appending "Negotiate" or "NTML" to the "WWW-Authenticate" header. I even tried forcing the transmission of the "Basic" header from jQuery using the "withCredentials: true" flag and the "beforeSend" method but no dice. For now I am just telling the user that they need to close the browser window. Not happy. – AnthonyVO Dec 16 '11 at 23:47
  • @AnthonyVO It sounds like IIS is trying to apply Windows authentication, in which case this won't work. This workaround only works with HTTP Basic authentication - if the web server is expecting an NTLM negotiation this won't work as NTLM is 4 step while basic is 2 step. I don't think browsers cache NTLM authentication in the same way though - HTTP basic is checking a token each page, while NTLM is a 4 step handshake. – Keith Dec 19 '11 at 11:41
  • Just notice your comment now. Very certain that I had basic authentication. Needed to support BB. – AnthonyVO May 05 '12 at 03:06
7

A couple of notes. A few people have said that you need to fire off a ajax request with invalid credentials to get the browser to drop it's own credentials.

This is true but as Keith pointed out, it is essential that the server page claims to accept these credentials for this method to work consistently.

On a similar note: It is NOT good enough for your page to just bring up the login dialog via a 401 error. If the user cancels out of the dialog then their cached credentials are also unaffected.

Also if you can please poke MOZILLA at https://bugzilla.mozilla.org/show_bug.cgi?id=287957 to add a proper fix for FireFox. A webkit bug was logged at https://bugs.webkit.org/show_bug.cgi?id=44823. IE implements a poor but functional solution with the method:

document.execCommand("ClearAuthenticationCache", "false");

It is unfortunate that we need to go to these lengths just to log out a user.

AnthonyVO
  • 3,821
  • 1
  • 36
  • 41
7

Mozilla implemented the crypto object, available via the DOM window object, which has the logout function (Firefox 1.5 upward) to clear the SSL session state at the browser level so that "the next private operation on any token will require the user password again" (see this).

The crypto object seems to be an implementation of the Web Crypto API, and according to this document, the DOMCrypt API will add even more functions.

As stated above Microsoft IE (6 upward) has: document.execCommand("ClearAuthenticationCache", "false")

I have found no way of clearing the SLL cache in Chrome (see this and this bug reports).

In case the browser does not offer any API to do this, I think the better we can do is to instruct the user to close the browser.

Here's what I do:

var agt=navigator.userAgent.toLowerCase();
if (agt.indexOf("msie") !== -1) {
    document.execCommand("ClearAuthenticationCache","false");
}
//window.crypto is defined in Chrome, but it has no logout function
else if (window.crypto && typeof window.crypto.logout === "function"){
    window.crypto.logout();
}
else{
    window.location = "/page/to/instruct/the/user/to/close/the/browser";
}
staromeste
  • 931
  • 7
  • 5
  • 2
    instead of checking the userAgent string, you should check if the command is supported by using: `if document.queryCommandSupported("ClearAuthenticationCache") { ... }` – Bart Jolling May 04 '17 at 13:09
3

I've been searching for a similar solution and came across a patch for Trac (an issue management system) that does this.

I've looked through the code (and I'm tired, so I'm not explaining everything); basically you need to do an AJAX call with guaranteed invalid credentials to your login page. The browser will get a 401 and know it needs to ask you for the right credentials next time you go there. You use AJAX instead of a redirect so that you can specify incorrect credentials and the browser doesn't popup a dialog.

On the patch (http://trac-hacks.org/wiki/TrueHttpLogoutPatch) page they use very rudimentary AJAX; something better like jQuery or Prototype, etc. is probably better, although this gets the job done.

Marius Marais
  • 956
  • 6
  • 7
2

Why not use FormsAuth, but against ActiveDirectory instead as per the info in this thread. It's just as (in)secure as Basic Auth, but logging out is simply a matter of blanking a cookie (or rather, calling FormsAuthentication.SignOut)

Community
  • 1
  • 1
Duncan Smart
  • 31,172
  • 10
  • 68
  • 70
  • I could look into the FormsAuth thing, but right now the policy is to use Windows authentication against the system. I'll check into all three of these options. If anybody else thinks of something, by all means post it. – Dillie-O Aug 28 '08 at 14:58
0

Well, I've been browsing around Bugzilla for a bit now and seemingly the best way you can go for clearing the authentication would be to send non-existant credentials.

Read more here: https://bugzilla.mozilla.org/show_bug.cgi?id=287957

Andy
  • 250
  • 1
  • 2
-1

Hopefully this will be useful until someone actually comes along with an explicit answer - this issue was discussed two years ago on a message board.

HTH

Jason Bunting
  • 58,249
  • 14
  • 102
  • 93