// HTTP digest authentication with PHP (safe mode enabled)

Doing HTTP digest authentication with PHP is an easy task. The manual entry is providing all needed information as long as you do not skip the last note on the page:

Note: If safe mode is enabled, the uid of the script is added to the realm part of the WWW-Authenticate header.

I just talked to one of my friends who did not notice the safe mode behavior, he had problems because HTTP digest authentication simply did not work on his server where safe mode is active. Unfortunately, the manual is not providing an example working with both active and inactive safe mode, therefore I am releasing one here. You may use the function directly… or better build a nice auth-class for doing the job. However, I think the example should help in both cases providing all needed information for creating your own HTTP digest authentication. Have fun. :-)

http-auth.php
<?php
/**
 * HTTP digest authentication
 *
 * @return true TRUE if everything worked/auth was successful.
 *         In case of errors and/or wrong credentials, the script will be killed
 *         (providing a message to the current client).
 * @author Andreas Haerter
 * @link http://en.wikipedia.org/wiki/Digest_access_authentication
 * @link http://de.wikipedia.org/wiki/HTTP-Authentifizierung
 * @link http://www.php.net/manual/features.http-auth.php
 * @link http://blog.andreas-haerter.com/2010/04/19/http-digest-authentication-with-php-safe-mode-enabled
 * @link http://www.php.net/manual/features.http-auth.php#93427
 */
function http_digest_authentication()
{
	//existing users/credentials
	$users = array("username1" => "password1",
	               "username2" => "password2");
 
	//message to show
	$realm = "Please enter your credentials";
 
	//send needed digest auth headers
	if (empty($_SERVER["PHP_AUTH_DIGEST"])) {
		header("HTTP/1.1 401 Unauthorized");
		header("WWW-Authenticate: Digest realm=\"".$realm."\",qop=\"auth\",nonce=\"".uniqid(mt_rand(), true)."\",opaque=\"".md5($realm."salt-for-opaque")."\"");
		die("unauthorized access");
	}
 
	//parse http digest (inspired through http://www.php.net/manual/features.http-auth.php#93427)
	$mandatory = array("nonce"    => true,
	                   "nc"       => true,
	                   "cnonce"   => true,
	                   "qop"      => true,
	                   "username" => true,
	                   "uri"      => true,
	                   "response" => true);
	$data = array();
	preg_match_all('@(\w+)=(?:(?:\'([^\']+)\'|"([^"]+)")|([^\s,]+))@', $_SERVER["PHP_AUTH_DIGEST"], $matches, PREG_SET_ORDER);
	foreach ($matches as $m) {
		$data[$m[1]] = $m[2] ? $m[2] : ($m[3] ? $m[3] : $m[4]);
		unset($mandatory[$m[1]]); //mandatory part was found, kick it out of the "to do" list (=$mandatory array)
	}
 
	//create valid digest to validate the credentials
	$digest = "";
	if (isset($users[$data["username"]])) {
		$realm_digest = $realm;
		//As mentioned at <http://www.php.net/manual/en/features.http-auth.php>:
		//If safe mode is enabled, the uid of the script is added to the realm part of
		//the WWW-Authenticate header (you cannot supress this!). Therefore we have to
		//do this here, too.
		if (6 > (int)PHP_VERSION //safe_mode will be removed in PHP 6.0
		    && (int)ini_get("safe_mode") !== 0) {
			$realm_digest .= "-".getmyuid();
		}
		$digest = md5(md5($data["username"].":".$realm_digest.":".$users[$data["username"]]) //A1
		              .":".$data["nonce"].":".$data["nc"].":".$data["cnonce"].":".$data["qop"].":"
		              .md5($_SERVER["REQUEST_METHOD"].":".$data["uri"]));                    //A2
	}
	if (empty($digest)
	    || $data["response"] !== $digest) {
		header("HTTP/1.1 401 Unauthorized");
		header("WWW-Authenticate: Digest realm=\"".$realm."\",qop=\"auth\",nonce=\"".uniqid(mt_rand(), true)."\",opaque=\"".md5($realm."salt-for-opaque")."\"");
		die("wrong credentials");
	}
	//if we are here, auth was successful
	return true;
}
?>
I'm no native speaker (English)
Please let me know if you find any errors (I want to improve my English skills). Thank you!
QR Code: URL of current page
QR Code: URL of current page start (generated for current page)