When browsers (or at least Firefox) send HTTP Basic Authentication headers

February 17, 2021

We're long term fans of using HTTP Basic Authentication in Apache, but while I know how to configure it in Apache (and even how to log out of it in Firefox), I haven't really looked into some of the finer details of how it works. In particular, until recently I hadn't looked into when the browser (or at least Firefox) sends the Authorization header in HTTP(S) requests and when it doesn't.

The simple story of how HTTP Basic Authentication (also) works is that when your browser requests a URL protected by Basic Authentication, Apache will answer with a HTTP 401 status and some additional headers. If your browser has relevant credentials cached, it will re-issue the HTTP request with an Authorization header added. If your browser doesn't have the credentials, it will prompt you for login information (in a process that's recently been improved) and then re-issue the request.

Of course this simple story would be rather bad for responsiveness, since it implies that the browser would make two HTTP requests for every URL protected by HTTP Basic Authentication (one without any authorization, which would get a 401, and then a retry with authorization). So browsers don't do that. Instead to some degree they treat the Authorization header like a cookie and preemptively send it along for at least some requests to your website. The question I was curious about was how broadly Firefox did that. Unfortunately for us, the answer is that Firefox doesn't send the Authorization header very broadly.

(This is an appropriate choice for security, of course.)

As far as I can tell from some simple experimentation, Firefox will preemptively send Authorization for any URL under a directory on your site where it's been challenged for HTTP Basic Authentication before (in the same Basic Authentication realm and so on). It won't preemptively send Authorization outside of the hierarchy under those directories. That's kind of abstract, so here's a concrete example.

Suppose I have a website with URLs (among others) of:

/grafana/
/grafana/d/overview/
/grafana/d/pingstatus/
/grafana/d/downhosts/
/alertmanager/
/statics/

All of these URLs other than the /statics/ hierarchy are protected by the same HTTP Basic Authentication, configured once for /grafana/ and everything underneath it and once for /alertmanager/ (and everything underneath it).

If I request the /grafana/d/overview/ dashboard in a clean session, I will get a 401 and then have to authenticate. If I then request the /grafana/d/pingstatus/ dashboard, Firefox will not preemptively send Authorization, because it's not in or under the first URL; instead it will get a 401 and then re-send the request. If I go to /grafana/ (the top level) Firefox will get a 401 again, but now if I go on to /grafana/d/downhosts/, Firefox will preemptively send Authorization because it's under a URL that Firefox has been challenged on.

(If /grafana/d/overview was a page instead of a directory, requesting /grafana/d/pingstatus afterward would preemptively send a Authorization header because they would both be under the /grafana/d/ directory.)

If I request /alertmanager/ or /statics/ after all of this, my Firefox won't send a preemptive Authorization because both of them are outside of /grafana/. Requesting /alertmanager/ without authentication will get a 401 and Firefox will resend the request with the Authorization header, but Firefox will never request /statics/ with an Authorization header. The /statics/ URL is outside of all HTTP Basic authentication directories and the web server itself will never reply with a 401 to trigger Firefox's sending of Authorization.

(If you want to think of it in cookie terms, I believe this is what would happen if the web server set a cookie with a Path= of the initial URL directory and then could add more paths as you clicked around the site.)

In HTTP, the server's HTTP 401 reply to the browser contains no (reliable) information that the browser can use to determine what URL hierarchy is covered by authentication. The HTTP server has no way of telling the browser 'this challenge is for all of /grafana/' (even though Apache knows that); it just gives 401s for all of those URLs when Firefox sends requests without an Authorization header. Eventually Firefox hopefully learns all of the URLs that need Basic Authentication that you (and Grafana) are actually using.


Comments on this page:

This behaviour is specified in the HTTP basic authentication RFC: https://tools.ietf.org/html/rfc7617#section-2.2

Written on 17 February 2021.
« How ZFS on Linux brings up pools and filesystems at boot under systemd
TLS certificates specifying hosts via their CommonName field is more or less gone »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Wed Feb 17 00:16:24 2021
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.