Relogin and HTTP Basic Auth

Web developers have long been aware of the problem of logging and logging in to sites protected by HTTP Basic Authorization. Although there are other authentication methods that do not suffer from this problem, Basic Authorization is still often the best choice. The network has enough materials describing various general and particular solutions. But all of them, which I found, unfortunately, describe only some partial solutions that work in one browser and do not work in another. Under cat I give a generalized end result of my research on this problem

I’ll make a reservation right away that I’m not a front-end developer, and I didn’t go into the whole variety of “brands” and browser versions. Only mainstream with versions relevant at the time of writing. For most web tasks this is enough. For those developers who need support for the entire "zoo" of browsers, the article can be a good starting point.

The essence of the problem is that the HTTP Basic Authorization standard does not provide for logging. Generally. When you go to a page protected by Basic Authorization, the browser itself displays you its own window with a request for login / password and, with a successful login, saves them somewhere in its depths. Then, all subsequent requests to other protected pages of this site will automatically use these values ​​in the Authorization header:
Authorization: Basic bm9uZTpub25l
where bm9uZTpub25l is the base64-encoded login / password link none: none (your username and password may be different)

In order to log in, you just need to reset the old username / password in those very depths of the browser and on them place to record new ones. This is where a surprise awaits us. Since no standard regulates this action, each browser does this on its own understanding.

Even worse, since the login / password request window is its own browser window, which already responds to HTTP page headers only, this window pops up before any processing of the page body. That is, to execute some javascript when receiving a server response with a status of 401 does not work. Before the user this window will pop out again and again with a repeated request for login / password.

In fact, this point is critical, since for almost all browsers the only way to log out is to send a request to the server with obviously wrong login / password, receive a server response with the status 401 and then reset the browser’s login / password cache. But if at the same time the browser does not allow you to process the response 401 and continue the login process already for the new user, taking control even at the stage of reading HTTP headers and throwing the very form of authorization into which you enter it will not work in your face, then this already a problem. It doesn’t matter if this is a normal request by reference or XMLHttpRequest or fetch. The browser still takes control at the stage of parsing the headers.

So…

Initial data:


  1. There is a separate logout.html page on which all the logic is located in the javascript script, parts of which are given in the course of presentation
  2. Url specified - page address to redirect after successful login
  3. Url401 is specified - the address of the page that always returns the HTTP 401 error (not authorized)

// file logout.html
const url = new URL("http://mysite.com/");
const url401 = new URL("http://mysite.com/401");

Internet explorer


Our product does not require support for this browser, so the solution I have not personally tested. Nevertheless, according to Google, there is a solution for it, and it is perhaps the simplest and most elegant of all. Moreover, I have never met any information that this solution for Microsoft browsers has lost its relevance:

if (document.execCommand("ClearAuthenticationCache")) {
    window.location.assign(url);
}

In IE, there is a ClearAuthenticationCache method that discards "those very deep bowels." Simple and elegant. And no dancing with the auxiliary page 401. I do not know if this method works in Edge. Probably yes.

The document.execCommand construct returns true if the method exists and "worked". Then window.location.assign (url) redirects the user to enter a new username and password

Firefox (72.0.1)


In the context of our task, this is the most problematic browser. For him, a complete solution still does not exist. For 15-20 years, a request for the indicated problem has been hanging in the bug tracker of the team of its developers. But "things are still there." The maximum that can be achieved is the curve razlogin.

Input:
Firefox does not reset the password cache after receiving a 401 response through either XMLHttpRequest or fetch request. Only through a regular request with the login / password in the URL itself. That is, something like
http: // none: none@mysite.com/
The code:

else if (/firefox|iceweasel|fxios/i.test(window.navigator.userAgent)) {
    url.username = 'none';
    url.password = 'none';
    window.location.assign(url);
}

After which the user receives a login / password input form, into which whatever you enter, it will pop up again and again. The fact is that the entered values ​​will not override the login / password values ​​specified in the URL. That is, instead of the entered values, a bunch of none: none will go to the server each time. And to log in under a different name, the user must click cancel, go to the start page (http: //mysite.com/) and enter a new login / password there.

Crooked? But alas, there is no other solution

Google Chrome (79.0.3945.88)


For Chrome, the fetch method works great. But XMLHttpRequest does not work (((The cache is not reset, and the logging does not occur. Moreover, I tried to set the login / password both as parameters for the open method and setting the header.

else if (/chrome/i.test(window.navigator.userAgent)) {
    fetch(url401, {
      credentials: 'include',
      headers: {
        'Authorization': 'Basic ' + btoa('none:none'),
        'X-Requested-With': 'XMLHttpRequest'
      }
    }).then(() => {
      window.location.assign(url);
    }).catch(err => console.error(err));
}

We make a fetch request to page 401 with obviously incorrect username / password, we get a 401 response from the server, the browser flushes its cache.

IMPORTANT! The server should NOT return a WWW-Authenticate header . Otherwise, the browser will take control, and redirects from page 401 will never happen. By convention, the server should not return this header if X-Requested-With: XMLHttpRequest is specified in the request . Therefore, the X-Requested-With header has been added to the request .

Safari (12)


For Safari, the situation is exactly the opposite: XMLHttpRequest works, but fetch does not work

else {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url401, true, 'none', 'none');
    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    xhr.onerror = function(err) {
      console.error(err);
    };
    xhr.onload = function () {
      window.location.assign(url);
    };
    xhr.send()
}

The actions are the same as in Chrome, only through XMLHttpRequest

Should work for versions of Safari 6+. Earlier versions had their own bugs. In particular, for example, in versions from 5.1, with each redirect, the browser forcibly re-requested authorization, which is why authorization with redirection to the final page did not work in principle. And in versions prior to 5.0, the logging did not work.

All Articles