How NOT to write .htaccess files (and how to solve undesired multiple username/password prompts for web page access)

Prompted by one of our support folk, I've been looking at a problem related to authentication involving our CGI servers where we have multiple IP addresses behind the one DNS name (i.e., cgi.our.domain resolving to the addresses of three separate servers: serverA.our.domain, serverB.our.domain, and serverC.our.domain) The problem is/was that the user would have to put in their user name and password details up to three times to get access. It was apparent early that the problem was related to having multiple servers and the way bind/named rotates the order in which it returns IP addresses. Anyway, there was diddly squat I could do about bind/named and so I turned my attention to the webserver configuration, which quickly led to the .htaccess file involved in the originally-reported problem. It was ~office/alloc/.htaccess and it looked very much like this:

AuthType basic
AuthName "User Account and Password"
AuthYP On
require group @admin_group @support_group
require user helpdesk simona gregg

<IfModule mod_ssl.c>
	SSLRequireSSL
	order deny,allow
	require valid-user
</IfModule>

<IfModule !mod_ssl.c>
	RedirectMatch /(.*)$ https://cgi.our.domain/$1
</IfModule>

There are two extremely large security problems with this. One is that it leaks plain-text passwords, and the other is that it actually doesn't limit access as apparently intended. The .htaccess file is apparently intended to do two things:

  1. Trap HTTP requests and redirect them to HTTPS so that user names and passwords (i.e., credential) are encrypted, and
  2. Limit access to the directory's contents to people in the given user list, or to those in the given groups.

It does neither of these things.

ONE

The way it should work is that the bit in the "<IfModule !mod_ssl.c>" conditional should send an HTTP 302 redirect back to a user's browser when they use "http://cgi.our.domain/~office/alloc". This redirect tells the browser to use an "https" URL instead. The problem here is that the parameters which turn on authentication are OUTSIDE any conditional and so apply both to HTTP and to HTTPS connections. Thus, the web server requires authentication BEFORE it will redirect and while still using HTTP. The average user won't have any indication that their credentials are being sent in the clear (well, base-64 encoded, actually, but that's not encryption) and thus there's the potential for leakage, particular if users access these pages from somewhere like China or some places in the Middle East where traffic monitoring is de rigueur. By the time the user has been redirected to HTTPS, their credentials have already been exposed.

TWO

Access control on the web server is done by specifying who should have access. This is done via "require" directives. There can be more than one of these and they are logically OR'ed together. Did you notice the "require valid-user" in the "<IfModule mod_ssl.c>" block? It probably got there accidentally due to copying and it completely usurps the previous "require group ..." and "require user ..." directives because it only requires a user to authenticate (i.e., to have a valid password), and this is true for all visitors, all students, all staff, and, well, everyone here. Below I will show a better .htaccess file...

Back to the original problem

The original problem of multiple authentications being required is related to the round-robin DNS issue. That is, it is an artefact of a different IP address being given to the web browser each time it tries to resolve "cgi.our.domain". What happens is something like this:

  1. Web browser looks up "cgi.our.domain" and gets address of web server A.
  2. Web browser sends request to web server A.
  3. Web server A says (via an HTTP 401 response), "Hey! You didn't send any authentication, bub! I need to know who you are." This causes the browser to prompt the user for their user name and password.
  4. Next, web browser looks up "cgi.our.domain" again to re-send the original request with the user credentials and this time gets the address of web server B.
  5. Web browser sends the request to web server B including user credentials.
  6. Web server B says, "Cool! This request contains the credentials I need and I didn't even ask for them!" It sends back a positive response omitting anything that indicates it requires credentials.
  7. Web browser prepares for the next request to the server by looking up "cgi.our.domain" and gets the address of web server C.
  8. Web browser sends request to C, this time omitting credentials because the response from web server B indicated none were required.
  9. Web server C says, "Hey! You didn't send any authentication, bub! I need to know who you are." This causes the browser to prompt the user for their user name and password.
  10. And round and round we go.

A better way

A better approach is:

<IfModule mod_ssl.c>
        Header set WWW-Authenticate "Basic realm=\"User Account and Password\""
        AuthType basic
        AuthName "User Account and Password"
        AuthYP On
        SSLRequireSSL
        order deny,allow
        require group @admin_group @support_group
        require user helpdesk simona gregg
#       require valid-user
</IfModule>

<IfModule !mod_ssl.c>
        RedirectMatch /(.*)$ https://cgi.our.domain/$1
</IfModule>

Here we see that all the authentication-related stuff has been moved into the HTTPS/SSL block and no authentication is needed for the redirect from HTTP to HTTPS. The "require valid-user" line has been commented out so that the group and user lists aren't overridden. A "header" directive has been added so that for each and every HTTPS request, the web server will report that it needs authentication so that even if a web browser seems to kindly provide credentials without being asked, the server will continue to insist on them. This prevents the mess that starts at step 6 above. Note that the realm in the "header" directive and the "authname" parameter are the same.

Sadly

There are lots of similarly faulty .htaccess files around.