2

I've been bringing myself up to speed on HTTP Basic Authentication.

I understand that this is fundamentally an insecure access mechanism (even when used over HTTPS, which it always should be) but I recognise that HTTP Basic Auth is not entirely without utility and I would like to be familiar with it, even if situations seldom arise in which I might deploy it.


My understanding so far:

After some reading, I understand that:

  • a server may request authorisation for a resource by returning 401 (unauthorised)
  • a WWW-Authenticate response header determines that the authentication to be used to access this resource will be Basic
  • HTTP Basic Authentication requires either that:
  • a) the user submit username and password via a browser-generated console; or that
  • b) once successfully submitted manually, the same username and password combination will be automatically submitted via an Authorization request header prepended to each HTTP request

So far, so good.


Issues to be aware of:

I also understand that there are some issues with HTTP Basic Auth which have evolved over time, like:

  • some browsers no longer accept URL syntax such as https://mylogin:mypassword@example.com/my-resource.html
  • where PHP is being run through CGI or FastCGI then the submitted Authorization Credentials will not be passed to $_SERVER['HTTP_AUTHORIZATION'] unless a hack is deployed - the most common recommendation being a URL rewrite via .htaccess mod_rewrite

and other issues which have persisted from the very beginning, like:

  • it's a non-trivial problem to "log out" of HTTP Basic Auth since it was never intended or designed to have a log-out mechanism

The missing piece of the puzzle:

However, I'm still confused, because even where the user (or the Authorization request header) has submitted valid authentication credentials... how does the server know they are valid?

In every document I have come across discussing the mechanics of HTTP Basic Authentication the discussion stops short of the point at which the credentials are actually authenticated.


Question:

How are the submitted credentials actually authenticated?

Where is the server comparing the submitted credentials to... anything?


Bonus Question:

N.B. This is related to my main question immediately above because my use of .htaccess and queryString parameters to convey credentials (see below) renders deployment of HTTP Basic Auth entirely redundant - if I go down this route, I can convey credentials using .htaccess and queryString parameters alone and I don't need to deploy HTTP Basic Auth at all.

As a way to circumvent the CGI/FastCGI issue, I often see variations of these .htaccess lines cited:

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

or

RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]

or

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0 

though my attempts to get any of these methods to populate PHP Environment Variables with the credentials have all proven unsuccessful.

Instead, I have deployed the following (using queryString parameters instead of Environment variables), successfully:

# WRITE HTTP BASIC AUTHENTICATION CREDENTIALS TO QUERY STRING
RewriteCond %{HTTP:Authorization} [NC]
RewriteCond %{QUERY_STRING} ^basicauth=login$ [NC]
RewriteRule ^my-document.php https://example.com/my-document.php?basicauth=login-submitted&credentials=%{HTTP:Authorization} [NC,L]

which appends the credentials as queryString parameters.

I am not unhappy with my own mod_rewrite solution above, but I am stumped that I cannot get Environment Variables working at all.

I'm idly wondering if there is something obvious I'm missing when it comes to the latter - like... might they be switched off in my PHP Configuration?

(And, if so, which entries would I need to check in PHPInfo to confirm that they were actually switched on and receptive to values transferred to them via mod_rewrite?)

Rounin
  • 27,134
  • 9
  • 83
  • 108
  • "which appends the credentials as queryString parameters" - Although there would seem to be something missing from the first _condition_ - as written it will never match? "I cannot get Environment Variables working at all" - Presumably you've tried a simple test like `SetEnvIf ^ ^ TEST=1` or `RewriteRule ^ - [E=TEST:1]`? And still this does not set an env var? (How are you checking the env var? With phpinfo()?) Do you have other directives in `.htaccess`? Any looping by the rewrite engine will cause env vars to be renamed with a `REDIRECT_` prefix on each additional loop. – MrWhite Oct 11 '21 at 23:30
  • 1
    Are you specifically asking about HTTP Basic Auth with regards to PHP? – MrWhite Oct 12 '21 at 00:52
  • @MrWhite - On reflection, I think I probably am yes. But I'm only concluding this now after the discussion below covered using HTTP Basic Auth within a non-LAMP stack. _(N.B. I don't have a Node.js background. I've only this year started to branch out from LAMP into Deno + koa and, apart from that, I've known nothing else but LAMP since 2009. Before 2009, I never touched server-side at all.)_ – Rounin Oct 12 '21 at 08:42
  • 1
    Ok, but using Apache as the authenticator is the obvious choice with HTTP Basic Auth IMO. The _server_ then does "know" whether the credentials are valid. No need to mess around with passing the `Authorization` header. Although I'm still curious why you are seemingly not seeing _any_(?) env vars being passed through to PHP? Did you try a simple test and examine the `$_SERVER` superglobal? – MrWhite Oct 14 '21 at 01:21

2 Answers2

2

The explanation stops short of the actual authentication because how you do this is entirely up to you.

The username and password are sent as clear text* to you. So, same as when someone would submit a login form with a username and password field, you can then decide what to do with it.

*: Yes, they are base64-encoded. But my point was that they aren't hashed or encrypted or anything like that.

So, you may...

  • Compare the username & password to some other clear-text value, for example from an environment variable
  • Hash the password and compare the hash with something, for example a hashed password in a database
  • Forward the credentials to some external authentication service
  • ...?

Example of how that could look on the server (this example assumes the use of node.js, Koa, koa-router, Mongoose and bcrypt, and for simplification purposes it assumes neither username nor password are allowed to contain a colon):

router.get('/protectedPage', async ctx => {
  const [authMethod, authData] = ctx.get('authorization')?.split(' ') ?? []
  
  if (authMethod === 'Basic') {
    try {
      const decoded = Buffer.from(authData, 'base64').toString()
      const [username, password] = decoded.split(':')
      
      // Find user in database and verify password
      const user = await User.findOne({ username })
      if (user && await bcrypt.compare(password, user.encryptedPassword)) {
        // User is authenticated now
        ctx.state.user = user
      }
    } catch (e) {
      console.error(`Failed to process auth header "${authData}"`, e)
    }
  }
  
  if (ctx.state.user) {
    return ctx.render('protectedPage')
  } else {
    return ctx.throw(401, null, {
      headers: { 'WWW-Authenticate': 'Basic realm="Protected Page"' }
    })
  }
})

Essentially, how you use the credentials is not in the scope of the mechanism.

(About logging out: I usually redirect people to https://_logout_@example.com or something like that, so that from now on the requests will use this invalid username and empty password, causing a 401 again. Note that this won't work if the page is in cache because the cached version would be delivered anyway, so this aspect may need extra consideration in that case - probably authenticated pages should not be cached anyway.)

In regards to the "insecure" aspect: It's not really insecure if you use HTTPS, because the aspect of "the credentials are sent in every request in clear text" is not relevant anymore then. It does however present other issues outlined in this answer, most importantly that nowadays clear-text password validation should intentionally be designed as a slow operation to avoid brute-force attacks, but with a system like basic auth where this has to be performed on every request, this puts a heavy burden on the server which can easily be abused for denial-of-service attacks.


About how to get the authorization header forwarded to PHP: Unless something else is configured in a strange way, setting CGIPassAuth on in Apache's configuration should be all that's needed.

From the docs:

CGIPassAuth allows scripts access to HTTP authorization headers such as Authorization, which is required for scripts that implement HTTP Basic authentication. Normally these HTTP headers are hidden from scripts. This is to disallow scripts from seeing user ids and passwords used to access the server when HTTP Basic authentication is enabled in the web server. This directive should be used when scripts are allowed to implement HTTP Basic authentication.

Afterwards, the $_SERVER['HTTP_AUTHORIZATION'] variable should be set properly, and then PHP can also parse the header for you automatically and provide $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'].

MrWhite
  • 43,179
  • 8
  • 60
  • 84
CherryDT
  • 25,571
  • 5
  • 49
  • 74
  • Re: _"The explanation stops short of the actual authentication because how you do this is entirely up to you."_ Aha. Thanks, @CherryDT. I needed to read that. This _is_ what I suspected, but none of the good handful of articles I've read have ever explicitly stated this. At this point my bonus question above comes into play with greater importance because if I can't access credentials in PHP without first using `.htaccess` to convert the credentials into `queryString` parameters... well, I can simply set up PHP-based authentication using parameters... I don't need **HTTP Basic Auth** at all. – Rounin Oct 11 '21 at 12:14
  • 1
    That's an issue with Apache, FastCGI and PHP though - not related to HTTP basic auth in itself. I don't have this problem when I use Caddy with PHP, or something entirely different like node.js or ASP.NET for instance. The benefit of using HTTP basic auth is to add a - well - _basic_ authentication mechanism to a page or endpoint easily. You don't need a login form because the browser will provide it, you don't need to keep the logged-in state because the browser will remember the credentials for subsequent requests, etc. - all you need is to verify those credentials. – CherryDT Oct 11 '21 at 13:23
  • In its most basic form, you just need something that says `if (authorization header != expected value) then { return 401 response with www-authenticate header; exit }` before the main processing is done, and you are done, your page or whatever is now protected against unauthorized use, in a way that's still convenient for users (they just need to input their credentials when prompted). – CherryDT Oct 11 '21 at 13:29
  • Right. But I can't access the `Authorization header` in PHP without going around the houses with `mod_rewrite` directives in `.htaccess` (and even then I can't get Environmental Variables working), so, as you say, it's an _"Apache, FastCGI and PHP"_ issue. Maybe the answer to why I've spent a week coming back to this repeatedly and ultimately failing to completely get my head around it (while, nevertheless learning a lot about HTTP headers, ENV variables etc.) is simply: _"Regardless of historic compatibility, LAMP stack as it exists today is largely incompatible with HTTP Basic Auth."_ – Rounin Oct 11 '21 at 13:36
  • 1
    Hm, I'm not sure why it's such a big issue though. It sounds like something fundamental is missing. As far as I know, the `Authorization` header is not forwarded for "security reasons" (assuming that _Apache_ would handle auth and PHP scripts shouldn't see the credentials then), but this is supposed to be changeable by just setting `CGIPassAuth On` in Apache's configuration or a `.htaccess` file. See http://httpd.apache.org/docs/2.4/en/mod/core.html#cgipassauth - This should make not only `HTTP_AUTHORIZATION` work but also the other related variables such as `PHP_AUTH_USER` and `PHP_AUTH_PW`. – CherryDT Oct 11 '21 at 15:07
  • You're right about `CGIPassAuth On` - that was one of the things I came across and tried several days ago. I couldn't get it working then, but I might give it another spin now. If I still can't get things working with LAMP, I'll shelve it for now and (when I next find an appropriate use-case) I'll try **HTTP Basic Auth** with Deno / koa when I have some more time to experiment. Thanks for all your help and encouragement. – Rounin Oct 11 '21 at 15:41
  • It would be more accurate to say that how it is done is up to the *implementation*. There are standard basic auth implementations such as one built into Apache: [PasswordBasicAuth - HTTPD - Apache Software Foundation](https://cwiki.apache.org/confluence/display/HTTPD/PasswordBasicAuth) – Stephen Ostermiller Nov 05 '21 at 08:59
1

So this is only from my understanding but I'm pretty confident about it...: If you are sending HTTP Basic Authentication you are supplying a username and password which is automatically encoded to base64 and sent in the header as for example: Basic ZG9kb3BhbmE6YXV0bw== , which when received in the header on the other side is in the headers authorization value and it still looks like: Basic YmFzaWM6YXV0bw== :) => a quick only base64 decode will return from this: dodopana:auto - which are the username:password I gave to the post.

So far so good?! :) now, the basic auth has been sent and received. the next step - is not a server step... it's the API or endpoint software itself that as it demands a token or a username and password... should validate the decrypted auth infront of the API suppliers credentials.

It's just passing through an encrypted string and the comparing and or validating of said header is now in the API software hands or should I say - the programmer should validate it...

yivi
  • 42,438
  • 18
  • 116
  • 138
Shlomtzion
  • 674
  • 5
  • 12
  • 2
    `base64` is not an encryption method, it's an encoding. Very important distinction! Encoding has zero value in terms of security. – CherryDT Oct 11 '21 at 11:26
  • Thank you, @Shlomtzion. You write: _"the API or endpoint software [...] should validate the [...] auth"_. This is the part I don't understand. From where is the API getting the credentials? No article I've seen discussing **HTTP Basic Auth** ever seems to expand on this. Can you expand your answer please to give an example? Many thanks. – Rounin Oct 11 '21 at 12:04
  • 1
    Just for example , say you have a secured connection and a table of tokens on your server (You are the API owner/creator here), now, you know which passwords are allowed for which users.... when a new API user contacts you , you supply him with a user and password and when those are passed in his API request you validate them across your platform however you designed it... What I am saying is... that when you supply a service like API you also create a security backbone for it... – Shlomtzion Oct 11 '21 at 12:14
  • Sorry, I've not been as clear as I could be. Let me rephrase: does the API get the submitted credentials from the Authorization header? If so, how? If not, where does it get the submitted credentials from? – Rounin Oct 11 '21 at 12:18
  • Yes exactly , the Auth Header that arrives with the call (and was filled by the caller) has the credentials supplied in him as a string in the Basic Auth Header for example... however in different auth methods there could be different ways to transfer the credentials. – Shlomtzion Oct 11 '21 at 12:41