Simple HTTP authentication for Drupal sites [updated]

Tags: 

There are lots of times when you need some simple password protection, typically via HTTP Authentication, on your Drupal site; my most common use being for publicly accessible development sites that I don't want accidentally indexed by search engines. Thanks to some simple code, based on something originally written by someone at Acquia (thank you, phantom coder!), it's pretty easy to add HTTP authentication to your site and then enable it per site instance; this will keep your codebase and your modules list pretty clean. FYI this works equally well on both Drupal 6 and 7.

There are three parts to this solution:

  1. Modifying the .htaccess file to allow the authentication.
  2. A function that can be added to settings.php or elsewhere in the execution process.
  3. A function call that is used as the on/off switch.

Step 1: Customize the .htaccess file

The first piece is a new line that needs to be added to the .htaccess file:

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

This should be added at the end of the file, just before the final , so that it looks like so:

  # Password protection.
  RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
</IfModule>

Step 2: Add the Special Function

All of the logic is housed in a PHP function that needs to be added to your site, probably the best location being your settings.php file.

/**
 * Password protect the site with a single function.
 */
function secure_the_site_please($username = 'monkey', $password = 'monkey', $message = 'This site is protected') {
  // Password protect this site but ignore drush and other command-line
  // environments.
  if (php_sapi_name() != 'cli') {
    // PHP-cgi fix.
    $a = base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6));
    if ((strlen($a) == 0) || (strcasecmp($a, ":") == 0)) {
      header('WWW-Authenticate: Basic realm="Private"');
      header('HTTP/1.0 401 Unauthorized');
    }
    else {
      list($entered_username, $entered_password) = explode(':', $a);
      $_SERVER['PHP_AUTH_USER'] = $entered_username;
      $_SERVER['PHP_AUTH_PW'] = $entered_password;
    }
    if (!(isset($_SERVER['PHP_AUTH_USER']) && ($_SERVER['PHP_AUTH_USER'] == $username && $_SERVER['PHP_AUTH_PW'] == $password))) {
      header('WWW-Authenticate: Basic realm="' . $message . '"');
      header('HTTP/1.0 401 Unauthorized');
      // Fallback message when the user presses cancel / escape.
      echo 'Access denied';
      exit;
    }
  }
}

Step 3: The on/off switch

The final step is to add the following line to the end of the settings.php file somewhere after the function above:

// Password protect the site.
secure_the_site_please();

This is the master on/off switch that lets you control the authentication functionality. You can easily disable it as needed just by commenting out the line, then the site will continue to work as-is without any problems.

You may have noticed that the function had some arguments? You can use these arguments to change the default username & password from "monkey" to something more relevant, or easily remembered, e.g.:

// Use a custom username & password.
secure_the_site_please('albatross', 'mint');

The function accepts a third argument which controls the message displayed so it can be changed from the default "This site is protected" to something more suitable, e.g.:

// Use a custom username & password.
secure_the_site_please('albatross', 'mint', 'What flavor is it?');

Or even:

// Use a custom username, password & message.
secure_the_site_please('albatross', 'mint', 'The username is "albatross" and the password is "mint"');

Final notes & thoughts

The main reason I like this solution vs e.g. SecureSite is that it doesn't require a different set of modules for one site vs another, all that's needed is one change to the settings.php file and it's easily turned on or off.

I include a settings_shared.php at the top of each settings.php file to store shared settings between all of the per-hostname site instances, and this is where I added the function. On one site I had 44 different per-hostname settings files for four different instances of eleven sites, half of which needed to be protected, so it was very easy to have the function in the settings_shared.php file and then just add the function call to the individual settings.php files.

The HTTP authentication variable, "HTTP_AUTHORIZATION", was so-named so it could be compatible with Drupad, which also uses HTTP authentication; please note that you currently must have this authentication disabled in order for Drupad to work.

So, what do you use to product your in-development sites from prying eyes or being accidentally scraped by search engines?

Updated April 9th:
Thanks for all the feedback. It turns out that the Shield module can basically do this too, using variables (that can be added to settings.php) to control everything; also the SecureSite module has an on/off switch variable so in effect it could be used too. FYI the reason I didn't use any Drupal 7 functions was so that the same code snippet would work with D6 too.

Updated April 12th:
Fixed a small =) bug that inadvertently allowed any password to be passed through, once the username matched.

10 Comments

Great, thanks. The contrib

Great, thanks. The contrib modules for doing have not been entirely satisfactory on D7 in my experience, so this is all the more welcome.

Delete post, replace with a

Delete post, replace with a link to shield module. Done!

Drupal 7 has the

Drupal 7 has the drupal_is_cli() function to check if Drupal is being invoked from a CLI (it's also available in Pressflow for Drupal 6, but hasn't been backported to Drupal 6 itself)…it checks a few more things than php_sapi_name() to cover a few other use-cases.

If you prefer a module-based approach, there's naturally a bunch of modules for doing similar things:
- Doorman (D7 only) - http://drupal.org/project/doorman - allows you to choose which pages are open; access to all other pages requires a Drupal login.
- Secure site (D6; D7 is in dev) - http://drupal.org/project/securesite - provides a number of different ways to lock down a site, including basic-auth.

Just wanted to say thanks for

Just wanted to say thanks for this. I'd been looking for an easy way to temporarily make a site private. There is also the secure site module (http://drupal.org/project/securesite) that is supposed to accomplish this but that caused some rather nasty errors. Your method, however, works just fine.

I've always used the Secure

I've always used the Secure Site module (http://drupal.org/project/securesite) for this. I like having the admin interface for it, because there are times that it needs to be disabled temporarily. For example, when testing Facebook sharing, Facebook needs to request the page to find available images for the thumbnail to show in the timeline. In order to test that, the site needs to be publicly available, and it's nice to have a solution that a PM can disable to do that testing.

I imagine that your method is a lot more convenient when you've got the extensive multi-site setup you explain. Thankfully, most of the multi-site installs I've done have been pretty simple, so it wasn't tedious to enable Secure Site on all affected sites.

Thanks for the feedback

Thanks for the feedback everyone, I've updated the post to mention some of it.

Is this how you are creating

Is this how you are creating these password protected areas on your site? Like: http://www.mc-kenna.com/protected-node?destination=node%2F1730&back=http...

Even though you mention

Even though you mention Shield module does basically the same thing, I still think you posting this is way cool. I first tried doing a regular Apache protection for my dev site but Drupal didn't like that much. After searching and trying other option I found your post and bang - done. Maybe someday I'll try the shield module, but for now, I have a "If it ain't broke don't fix it" mind set.

Thanks