PHP Web Fundamentals Using HTTP Basic or Digest Authentication - Supercoders | Web Development and Design | Tutorial for Java, PHP, HTML, Javascript PHP Web Fundamentals Using HTTP Basic or Digest Authentication - Supercoders | Web Development and Design | Tutorial for Java, PHP, HTML, Javascript

Breaking

Post Top Ad

Post Top Ad

Tuesday, May 28, 2019

PHP Web Fundamentals Using HTTP Basic or Digest Authentication

PHP  Web Fundamentals




Using HTTP Basic or Digest Authentication

Problem

You want to use PHP to protect parts of your website with passwords. Instead of storing the passwords in an external file and letting the web server handle the authentication, you want the password verification logic to be in a PHP program.

Solution

The $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] superglobal variables contain the username and password supplied by the user, if any. To deny access to a page, send a WWW-Authenticate header identifying the authentication realm as part of a response with HTTP status code 401:

          http_response_code(401);
          header('WWW-Authenticate: Basic realm="My Website"');
          echo "You need to enter a valid username and password.";
          exit();

Discussion

When a browser sees a 401 header, it pops up a dialog box for a username and password. Those authentication credentials (the username and password), if accepted by the server, are associated with the realm in the WWW-Authenticate header. Code that checks authentication credentials needs to be executed before any output is sent to the browser, because it might send headers. For example, you can use a function such as vali date().

Example   validate( )

          function validate($user, $pass) {
                /* replace with appropriate username and password checking,
                     such as checking a database */
                $users = array('david' => 'fadj&32',
                                            'adam' => '8HEj838');

                if (isset($users[$user]) && ($users[$user] === $pass)) {
                     return true;
                } else {
                     return false;
                }
          }

Example   shows how to use validate().

Example   Using a validation function

          if (! validate($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
               http_response_code(401);
               header('WWW-Authenticate: Basic realm="My Website"');
               echo "You need to enter a valid username and password.";
               exit;
          }    

Replace the contents of the validate() function with appropriate logic to determine if a user entered the correct password. You can also change the realm string from My Website and the message that gets printed if a user hits Cancel in her browser’s authentication box from You need to enter a valid username and password.

PHP supports Digest authentication in addition to Basic authentication. With Basic authentication, usernames and passwords are sent in the clear on the network, just minimally obscured by Base64 encoding. With Digest authentication, however, the password itself is never sent from the browser to the server. Instead, only a hash of the password with some other values is sent. 

This reduces the possibility that the network traffic could be captured and replayed by an attacker. The increased security provided by Digest authentication means that the code to implement is more complicated than just a simple password comparison. Example provides functions that compute digest
authentication as specified in RFC 2617.

Example   Using Digest authentication

          /* replace with appropriate username and password checking,
                such as checking a database */
          $users = array('david' => 'fadj&32',
                                      'adam' => '8HEj838');
          $realm = 'My website';

          $username = validate_digest($realm, $users);
          // Execution never reaches this point if invalid auth data is provided
          print "Hello, " . htmlentities($username);

          function validate_digest($realm, $users) {
                 // Fail if no digest has been provided by the client
                 if (! isset($_SERVER['PHP_AUTH_DIGEST'])) {
                       send_digest($realm);
                 }
                 // Fail if digest can't be parsed
                 $username = parse_digest($_SERVER['PHP_AUTH_DIGEST'], $realm, $users);
                 if ($username === false) {
                       send_digest($realm);
                 }
                 // Valid username was specified in the digest
                 return $username;
          }

          function send_digest($realm) {
                 http_response_code(401);
                 $nonce = md5(uniqid());
                 $opaque = md5($realm);
                 header("WWW-Authenticate: Digest realm=\"$realm\" qop=\"auth\" ".
                               "nonce=\"$nonce\" opaque=\"$opaque\"");
                 echo "You need to enter a valid username and password.";
                 exit;
          }

          function parse_digest($digest, $realm, $users) {
                 // We need to find the following values in the digest header:
                 // username, uri, qop, cnonce, nc, and response
                 $digest_info = array();
                 foreach (array('username','uri','nonce','cnonce','response') as $part) {
                        // Delimiter can either be ' or " or nothing (for qop and nc)
                        if (preg_match('/'.$part.'=([\'"]?)(.*?)\1/', $digest, $match)) {
                               // The part was found, save it for calculation
                               $digest_info[$part] = $match[2];
                        } else {
                               // If the part is missing, the digest can't be validated;
                               return false;
                        }
                 }
                 // Make sure the right qop has been provided
                 if (preg_match('/qop=auth(,|$)/', $digest)) {
                      $digest_info['qop'] = 'auth';
                 } else {
                       return false;
                 }
                 // Make sure a valid nonce count has been provided
                 if (preg_match('/nc=([0-9a-f]{8})(,|$)/', $digest, $match)) {
                      $digest_info['nc'] = $match[1];
                 } else {
                       return false;
                 }

                 // Now that all the necessary values have been slurped out of the
                 // digest header, do the algorithmic computations necessary to
                 // make sure that the right information was provided.
                 //
                 // These calculations are described in sections 3.2.2, 3.2.2.1,
                 // and 3.2.2.2 of RFC 2617.
                 // Algorithm is MD5
                 $A1 = $digest_info['username'] . ':' . $realm . ':' .
                         $users[$digest_info['username']];
                 // qop is 'auth'
                 $A2 = $_SERVER['REQUEST_METHOD'] . ':' . $digest_info['uri'];
                 $request_digest = md5(implode(':', array(md5($A1), $digest_info['nonce'],
                         $digest_info['nc'],
                 $digest_info['cnonce'], $digest_info['qop'], md5($A2))));

                 // Did what was sent match what we computed?
                 if ($request_digest != $digest_info['response']) {
                         return false;
                 }

                 // Everything's OK, return the username
                 return $digest_info['username'];
          }

Neither HTTP Basic nor Digest authentication can be used if you’re running PHP as a CGI program. If you can’t run PHP as a server module, you can use cookie authentication, discussed.

Another issue with HTTP authentication is that it provides no simple way for a user to log out, other than to exit his browser. The PHP online manual has a few suggestions for log out methods that work with varying degrees of success with different server and browser combinations.

There is a straightforward way, however, to force a user to log out after a fixed time interval: include a time calculation in the realm string. Browsers use the same username and password combination every time they’re asked for credentials in the same realm. By changing the realm name, the browser is forced to ask the user for new credentials.

Example   uses Basic authentication and forces a logout every night at midnight.

Example   Forcing logout with Basic authentication

          if (! validate($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'])) {
               $realm = 'My Website for '.date('Y-m-d');
               http_response_code(401);
               header('WWW-Authenticate: Basic realm="'.$realm.'"');
               echo "You need to enter a valid username and password.";
               exit;
          }

You can also have a user-specific timeout without changing the realm name by storing the time that a user logs in or accesses a protected page. The validate_date() function in example stores login time in a database and forces a logout if it’s been more than 15 minutes since the user last requested a protected page.

Example   validate_date( )

          function validate_date($user,$pass) {
                $db = new PDO('sqlite:/databases/users');

                // Prepare and execute
                $st = $db->prepare('SELECT password, last_access
                                                      FROM users WHERE user LIKE ?');
                $st->execute(array($user));

                if ($ob = $st->fetchObject()) {
                       if ($ob->password == $pass) {
                            $now = time();
                            if (($now - $ob->last_access) > (15 * 60)) {
                                   return false;
                            } else {
                                   // update the last access time
                                   $st2 = $db->prepare('UPDATE users SET last_access = "now"
                                                                            WHERE user LIKE ?');

                                   $st2->execute(array($user));
                                   return true;
                            }
                      }
               }

               return false;
        }


No comments:

Post a Comment

Post Top Ad