Skip to content

Instantly share code, notes, and snippets.

@emarref
Created July 27, 2012 04:12
Show Gist options
  • Save emarref/3186139 to your computer and use it in GitHub Desktop.
Save emarref/3186139 to your computer and use it in GitHub Desktop.
Class that handles HTTP Digest Authentication
<?php
class HttpDigestAuthentication
{
private $realm = 'Restricted area';
private $logins = array();
private $digestProperties = array();
private $authenticatedAs;
public function __construct(array $logins)
{
$this->logins = $logins;
}
public function isAuthenticated()
{
return $this->hasProvidedCredentials() && $this->isValidCredentials();
}
public function isAnonymous()
{
return !$this->isAuthenticated();
}
protected function hasProvidedCredentials()
{
return isset($_SERVER['PHP_AUTH_DIGEST']);
}
public function authenticate()
{
try {
$this->authenticatedAs = $this->isValidCredentials();
} catch (HttpDigestAuthenticationException $ex) {
$this->requireCredentials();
}
}
protected function isValidCredentials()
{
if (!$this->hasProvidedCredentials()) {
throw new HttpDigestAuthenticationException('No credentials provided');
}
if (false === $this->_parseHttpDigest()) {
throw new HttpDigestAuthenticationException('Could not parse http digest');
}
$username = $this->getDigestProperty('username');
error_log('Authenticating '.$username);
if (!isset($this->logins[$username])) {
// Attempted username does not exist
throw new HttpDigestAuthenticationException('Incorrect username');
}
$password = $this->logins[$username];
$validResponse = md5(sprintf(
'%s:%s:%s:%s:%s:%s',
md5(sprintf('%s:%s:%s', $username, $this->realm, $password)),
$this->getDigestProperty('nonce'),
$this->getDigestProperty('nc'),
$this->getDigestProperty('cnonce'),
$this->getDigestProperty('qop'),
md5(sprintf('%s:%s', $_SERVER['REQUEST_METHOD'], $this->getDigestProperty('uri')))
));
if ($this->getDigestProperty('response') != $validResponse) {
// Username is valid, but password is incorrect
throw new HttpDigestAuthenticationException('Incorrect password');
}
return $username;
}
public function requireCredentials($message = 'Restricted area')
{
ob_end_clean();
header('HTTP/1.1 401 Unauthorized');
header(
sprintf('WWW-Authenticate: Digest realm="%s",qop="auth",nonce="%s",opaque="%s"',
$this->getRealm(),
$this->_generateNonce(),
$this->_generateOpaque()
));
exit($message);
}
public function setRealm($realm)
{
$this->realm = $realm;
}
public function getRealm()
{
return $this->realm;
}
protected function _generateNonce()
{
return uniqid();
}
protected function _generateOpaque()
{
return md5($this->realm);
}
public function getCredentials()
{
if ($this->isAuthenticated()) {
return array('username' => $this->authenticatedAs, 'password' => $this->logins[$this->authenticatedAs]);
} else {
return false;
}
}
protected function getDigestProperty($key, $default = null)
{
return isset($this->digestProperties[$key]) ? $this->digestProperties[$key] : $default;
}
private function _parseHttpDigest()
{
$neededParts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($neededParts));
$matches = array();
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($neededParts[$m[1]]);
}
$this->digestProperties = $neededParts ? false : $data;
}
}
class HttpDigestAuthenticationException extends Exception {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment