Implementation of HTTP Digest Authentication in PHP

So I could only find one other PHP based HTTP digest auth example on the internet…and it looked as though it might not even work. I wrote an abstract class as a base that allows you to easily build your own implementation.

You’d use it like so:

class MyAuth extends HTTPDigestAuth {
    // Implementation of abstract methods
}

$authenticator = new MyAuth();
$user = $authenticator->authenticate();

if(!$user) {
    die();
}

The HTTPDigestAuth class looks like:

/*
Copyright 2010 Alan Shaw

http://freestyle-developments.co.uk

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations
under the License.
*/

/**
 * Object orientated PHP HTTP digest authentication.
 *
 * Extend this class and implement abstract functions to create your own
 * HTTP digest authentication implementation.
 */
abstract class HTTPDigestAuth {

    ////////////////////////////////////////////////////////////////////////
    // @public

    /**
     * @return an authenticated user object on success, null otherwise.
     */
    public function authenticate() {

        if(empty($_SERVER['PHP_AUTH_DIGEST'])) {
            $this->setHeadersUnauthorized();
            $this->getResponseBodyUnauthorized();
            return null;
        }

        $authClientData = new HTTPDigestAuthClientData($_SERVER['PHP_AUTH_DIGEST']);

        // Check for stale nonce
        if($this->isStaleNonce($authClientData->nonce)) {
            $this->setHeadersUnauthorized(true);
            $this->getResponseBodyUnauthorized();
            return null;
        }

        // Check for correct nonce count
        if($authClientData->nc != $this->getNonceCount($authClientData->nonce) + 1) {
            $this->setHeadersBadRequest();
            $this->getResponseBodyBadRequest('Incorrect nonce count');
            return null;
        }

        $this->incrementNonceCount($authClientData->nonce);

        // Check request URI is the same as the auth digest uri
        if($authClientData->uri != $_SERVER['REQUEST_URI']) {
            $this->setHeadersBadRequest();
            $this->getResponseBodyBadRequest('Digest auth URI != request URI');
            return null;
        }

        // Check opaque is correct
        if($authClientData->opaque != $this->getOpaque()) {
            $this->setHeadersBadRequest();
            $this->getResponseBodyBadRequest('Incorrect opaque');
            return null;
        }

        // Check user exists
        if(!$this->userExists($authClientData->username)) {
            $this->setHeadersUnauthorized();
            $this->getResponseBodyUnauthorized();
            return null;
        }

        $ha1 = $this->getHA1ForUser($authClientData->username);

        // Generate A2 hash
        if($authClientData->qop == 'auth-int') {
            $a2 = $_SERVER['REQUEST_METHOD'] . ':' . stripslashes($_SERVER['REQUEST_URI']) . ':' . file_get_contents('php://input');
            $ha2 = md5($a2);
        } else {
            $a2 = $_SERVER['REQUEST_METHOD'] . ':' . stripslashes($_SERVER['REQUEST_URI']);
            $ha2 = md5($a2);
        }

        // Generate the expected response
        if($authClientData->qop == 'auth' || $authClientData->qop == 'auth-int') {
            $expectedResponse = md5($ha1 . ':' . $authClientData->nonce . ':' . $authClientData->nc . ':' . $authClientData->cnonce . ':' . $authClientData->qop . ':' . $ha2);
        } else {
            $expectedResponse = md5($expectedResponse = $ha1 . ':' . $authClientData->nonce . ':' . $ha2);
        }

        // Check request contained the expected response
        if($authClientData->response != $expectedResponse) {
            $this->setHeadersBadRequest();
            $this->getResponseBodyBadRequest();
            return null;
        }

        return $this->getUser($authClientData->username);
    }

    ////////////////////////////////////////////////////////////////////////
    // @private

    private function setHeadersUnauthorized($stale = false) {

        header('HTTP/1.1 401 Unauthorized');

        $authHeader = 'WWW-Authenticate: Digest realm="' . $this->getAuthRealm() . '",qop="auth-int,auth",algorithm="MD5",nonce="' . $this->createNonce() . '",opaque="' . $this->getOpaque() . '"';

        if($stale) {
            $authHeader .= ',stale=TRUE';
        }

        header($authHeader);
    }

    private static function setHeadersBadRequest() {
        header('HTTP/1.1 400 Bad Request');
    }

    ////////////////////////////////////////////////////////////////////////
    // @optional

    protected function getResponseBodyUnauthorized($reason = '') {
?>
<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Error</title>
</head>
<body>
    <h1>401 Unauthorized.</h1>
<?php
if($reason) {
?>
    <p><?php echo htmlspecialchars($reason); ?></p>
<?php
}
?>
</body>
</HTML>
<?php
    }

    protected function getResponseBodyBadRequest($reason = '') {
?>
<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Error</title>
</head>
<body>
    <h1>400 Bad Request.</h1>
<?php
if($reason) {
?>
    <p><?php echo htmlspecialchars($reason); ?></p>
<?php
}
?>
</body>
</HTML>
<?php
    }

    ////////////////////////////////////////////////////////////////////////
    // @required

    /**
     * Gets the authentication realm for this class
     *
     * @return String
     */
    abstract protected function getAuthRealm();

    /**
     * Gets the opaque for this class
     *
     * @return String
     */
    abstract protected function getOpaque();

    /**
     * Creates a new nonce to send to the client
     *
     * @return String
     */
    abstract protected function createNonce();

    /**
     * Returns whether or not this nonce has expired. Should return true for
     * non existent nonce.
     *
     * @param String $nonce
     * @return Boolean
     */
    abstract protected function isStaleNonce($nonce);

    /**
     * Gets the current request count for a particular nonce
     *
     * @param String $nonce The nonce to get the count of
     * @return uint The current nonce count
     */
    abstract protected function getNonceCount($nonce);

    /**
     * Increments the nonce count by 1
     *
     * @param String $nonce The nonce to increment
     */
    abstract protected function incrementNonceCount($nonce);

    /**
     * Returns a boolean indicating whether or not a user with the specified
     * username exists.
     *
     * @param String $username
     * @return Boolean
     */
    abstract protected function userExists($username);

    /**
     * Returns the A1 hash for the specified user.
     * i.e. return md5('username:realm:password')
     *
     * @param String $username
     * @return String
     */
    abstract protected function getHA1ForUser($username);

    /**
     * Returns a user instance that belongs to the user with the username
     * provided.
     *
     * @param String $username
     * @return ???
     */
    abstract protected function getUser($username);
}

/**
 * @private
 */
class HTTPDigestAuthClientData {

    public $username;
    public $nonce;
    public $nc;
    public $cnonce;
    public $qop;
    public $uri;
    public $response;
    public $opaque;

    public function __construct($header) {

        preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response|opaque)=[\'"]?([^\'",]+)@', $header, $t);

        $data = array_combine($t[1], $t[2]);

        $this->username = $data['username'];
        $this->nonce = $data['nonce'];
        $this->nc = $data['nc'];
        $this->cnonce = $data['cnonce'];
        $this->qop = $data['qop'];
        $this->uri = $data['uri'];
        $this->response = $data['response'];
        $this->opaque = $data['opaque'];
    }
}

You can download the full source here: github.com/alanshaw/php-http-digest-auth

21 thoughts on “Implementation of HTTP Digest Authentication in PHP

  1. Hi,
    thank you very much for your script. It looks professional. But can you please help me with the nounce? How I have to implement the methods getNonceCount, incrementNonceCount and createNonce?
    Thank you!
    BMo

  2. “createNonce” will be called when a client initially connects to your service – you have to create a nonce value to send back to the client e.g. md5(uniqid()); At this point you also need to persist this information somehow – to a database for example. You’ll need to store the nonce you’ve created, the current nonce count (zero at this point) and the date it was created/modified, so you can check for stale nonces and purge old data.

    “getNonceCount” will be called for subsequent requests. It passes a nonce and you should query your persistent storage and return the recorded nonce count.

    If the client sends data using a nonce count that tallies with the value returned by “getNonceCount” then “incrementNonceCount” will be called – this signals that you should increment the current nonce count in your persistent storage by one. This is so that the next request from the client will match your current nonce count.

    Hope that helps

  3. I’ve been playing with this for hours and I still can’t get it to verify myself..lol Any other tricks or suggestions you might offer?

  4. The nonce returned by the browser is always slightly different from the one sent from the server…However the timestamp between them never changes (It’s encoded in the nonce in plain text)….

  5. Your style is unique compared to other folks I’ve read stuff from.
    I appreciate you for posting when you’ve got the opportunity,
    Guess I will just bookmark this web site.

  6. I do not even know how I ended up here, but I thought this post
    was good. I do not know who you are but certainly you are going to a famous blogger if you
    are not already ;) Cheers!

  7. You are so cool! I do not think I’ve truly read through
    anything like that before. So nice to find someone with
    a few genuine thoughts on this issue. Seriously..
    many thanks for starting this up. This web site is one thing
    that’s needed on the web, someone with a little originality!

  8. Howdy! I’m at work browsing your blog from my new iphone 3gs!
    Just wanted to say I love reading through your blog and look forward
    to all your posts! Keep up the superb work!

  9. Hello there! This post couldn’t be written mch better!
    Going through this article reminds me of my previous
    roommate! He always kept talking about this. I most certainly
    will send this information to him. Fairly certain he’ll hzve a great read.
    Many thaznks for sharing!

  10. whoah this blog is excellent i love reading your posts. Keep up
    the good work! You realize, lots of persons are looking round for this
    information, you could help them greatly.

  11. Wow! At last I got a weblog from where I be able to truly take useful data concerning my study and knowledge.

  12. Casino Characteristics – The Two Types of Player

    While the majority of casino destinations
    in the United States focus totally on the gambling facet of
    attracting visitors, the town of Reno, Nevada has had a much different approach.
    The first and foremost concept of Reno is usually to shape a town that
    is great to reside in, with plenty of choices
    of activities and cultural events. In this
    manner, travelers should come for many reasons and town will not succeed or fail depending on how the casinos
    fare. Making Reno an entire vacation destination is among the key concepts which will put it light
    years in front of the competition.

    The main reason behind the shift in laws is
    always that too many people have made it a habit to generate debt that they cannot repay.
    Lawsuits have pardoned the debt, paid lawyer fees completely and left credit histories untouched.

    This is great for the people indebted, but detrimental to the credit
    card companies who turn out losing a lot of money.

    The president, while using the powers granted to him in Article 180 in the Constitution and since the crackdown on gambling was called ‘an imperative
    of universal consciousness’, whereas the criminal law of all cultured contains provisions aimed at this purpose, given that ‘traditional moral, legal
    and religions in the Brazilian people is up against the practice
    and operation of gambling’. The exceptions offered to the general law passed shelters of those casinos which were labeled as
    ‘harmful to morals and good manners’, as well as the licenses and concessions
    to the practice of gambling inside city of Rio de Janeiro and also the hotels,
    were given a temporary basis and may be revoked at any time if
    they didn’t close on their own.

    1. Location, location, location: Do you get tired of waiting for the fifteen commercials before a film
    at the theaters to end before you actually see what you paid to see?
    Do you need something to complete while you await your food
    arrive at you? What about those waits in the doctor?

    The great thing about wireless casinos, in particular those
    you can play by way of a mobile phone, is you no longer ought
    to wait till you will get home to benefit from the fun.

    Web-based Casinos allow the player to play the casino game of their choice on the internet, with
    no need to download the casinos software with their computer.
    The casino games are usually delivered in Macromedia Flash, Java, or Macromedia
    Shockwave and will require your internet browser to get the
    relevant plug-in. The plug-ins cost nothing to and straightforward install if you do not already have them enabled
    on your web browser. Most browsers curently have the plug-ins installed.
    You will also desire a decent bandwidth because the casino games are made up of sounds and
    animations requiring decent band-with to operate properly.

  13. Hi,Alan,Thanks for sharing.
    I want to use your “HTTPDigestAuth.php” to implement “Digest Authentication”,according to the usage you provided.I implemented these follow functions: getAuthRealm(),getOpaque(),createNonce(),but another functions(isStaleNonce($nonce),getNonceCount($nonce),incrementNonceCount($nonce),userExists($username),getHA1ForUser($username),getUser($username)) I don’t know how to implement.
    I refer to this resource http://www.ietf.org/rfc/rfc2617.txt, there are some examples which explain how to calculate A1 (e.g: Mufasa:myhost@testrealm.com:Circle Of Life) in the resource ,but it isn’t same as your HTTPDigestAuth class, and there isn’t enough useful information about this on chinese website(I’m a Chinese, and my English skill isn’t well), so I don’t know how to continue.
    I hope you can provide some guidance, Thank you very much!

  14. Es ist ein erstaunliche Poost für alle online Zuschauer; sie nehmen bekommen Vorteil von ihm
    bbin ich mir sicher.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>