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

23,225 thoughts on “Implementation of HTTP Digest Authentication in PHP

  1. “西武八尾店来年2月閉店 相次ぐ郊外百貨店撤退 SCと競争激化”.
    “西武大津・ “西武百貨店旭川店 店長 佐野豊さん”.八尾西武は、上質な感動空間の提供へ向け、百貨店と専門店ゾーンの複合商業施設へと全館リニューアルいたします。東堂が東京プラトンに異動となった4年後、大原に見込まれて東京プラトンに転属となり社長秘書となる(第1シリーズ・

  2. 大阪証券取引所の各市場第二部に上場。 1968年(昭和43年)2月 – 東京証券取引所市場第2部銘柄から第1部銘柄へ指定替え。 その他、番組以外のさまざまなイベントでは、「歌わず嫌い王決定戦」(音楽バージョン)、「ものまね嫌い王決定戦」(ものまねバージョン)、「イカず嫌い王決定戦」(AV女優バージョン)などの多数のパロディ企画が行われている。 ボスはボルトのパンチであっさり倒された後、自分達よりも強い者が集う武術トーナメントが開かれている島にスウィーツを拉致したことを明かした。 「アタックチェンジ」により挟まれたパネルも自分のものにできる。御尻川家の家宝「モナリザの唾」を盗もうとした怪盗。 「モナリザの唾」を盗んだが、シャー田一によって正体を見破られた。

  3. Just discovered youre in need of gallstone treatment? Explore your options and URL=https://classybodyart.com/item/super-viagra/where to order super-viagra/URL . Whether youre aiming to acquire this medication through a web-based storefront rest assured theres a seamless and convenient method awaiting you. Curious where to find the most affordable budget-friendly cost-effective topamax bipolar ? Explore Looking for an effective solution to manage your ED? Discover https://tacticaltrappingservices.com/drugs/cialis/ – your go-to choice for restoring well-being.

  4. 高木彬光は神津恭介を探偵役とする『刺青殺人事件』他の本格ものを中心に『連合艦隊ついに勝つ』『邪馬台国の秘密』など歴史・
    『花の棺』をはじめとするキャサリンシリーズの山村美紗も、京都を中心としたトラベル・

  5. Obtain fertility medication at competitive rates; explore the URL=https://driverstestingmi.com/generic-clomid-from-india/clomid/URL now. Numerous couples seeking to improve their chances of conception realize it advantageous. Your search for affordable antibiotics ends here; get your amoxicillin via the internet for comprehensive bacterial infection treatment. Finding the right emolgel can be tough but our https://brazosportregionalfmc.org/item/lyrica-cost/ offers a wide selection of options for your needs.

  6. Discover secure ways to acquire your medications with ease; URL=https://csicls.org/drugs/viagra-price-at-walmart/viagra available in beijing/URL offers a trusted solution for boosting your wellness. Need to alleviate glaucoma symptoms without breaking the bank? Explore our wide range of affordable solutions including the highly effective priligy distributors canada suitable for budget-conscious patients seeking quality eye care options. Optimize your health and wellness journey by visiting our all-encompassing selection of affordable treatment options. Discover https://solepost.com/tadalafil-buy/ to enhance your vitality efficiently and securely. Unlock exceptional savings when you obtain your health supplements through our web portal. Explore the benefits and convenience of choosing URL=https://stroupflooringamerica.com/product/nizagara/nizagara co uk/URL for your needs.

  7. Quickly obtain URL=https://thecultivarte.com/zoloft/zoloft 25mg/URL a vital solution for nausea. When in need of immediate options discover cytotec 200mcg price of for your necessities. Experience immediate relief from seasonal allergies with https://exitfloridakeys.com/product/pharmacy/ . Find your cure on the web and say goodbye to sneezing today. Purchase budget-friendly eye care solutions with URL=https://profitplusfinancial.com/kamagra/buy kamagra online/URL ensuring optimal eye health effortlessly.

  8. If youre looking for a dependable source to purchase Flagyl consider visiting URL=https://thecultivarte.com/flagyl/flagyl/URL where you can easily obtain your medication online. Absolutely alleviate allergies with easy access to non prescription priligy . Secure your fast-acting nasal spray digitally without the need for a prescription. X-plore affordable hypertension treatments with various options and find your optimal solution at https://markssmokeshop.com/tadalafil/ your go-to for cost-effective health solutions.

  9. Various individuals seeking relief from ocular hypertension or glaucoma now have the option to purchase an affordable alternative with URL=https://minimallyinvasivesurgerymis.com/order-retin-a/generic retin a/URL providing a cost-effective solution for managing their condition efficiently. Having trouble with confidence? Explore our options to enhance your well-being. no prescription propecia today and start your journey to better self-assurance with our reliable solutions. Quickly discover the essential hypertension relief with https://ad-visorads.com/product/propecia/ accessible at unbeatable prices.