With PHP-based websites that I create, I use a very simple but secure method for handling authentication. This basic method is secure, protects the privacy of the user, and works with any browser that supports cookies.

1. The database

The first thing to do is create a user table in the database.

CREATE TABLE user (
    username VARCHAR(32) PRIMARY KEY,
    password_sha1 VARCHAR(40) NOT NULL
);

The main thing to note here is that the password should never be stored in the database and it should not be recoverable. Here, I am storing an SHA1 hash, not the actual password. In fact, you can generally judge the security of a website by whether or not it’s possible to get them to send you your password.

If a user forgets their password, a new random one should be generated, updated in the user table, and sent to the user. When they sign into the site, they should be reminded to change their password to something they can remember.

2. The sign in page

There is nothing special at all about the sign in page. It is simply a form with a username and password field. This is, however, the weakest point in the security of this authentication scheme. Be sure that this page is encrypted with SSL, or else the password they enter will be sent across the Internet in plain text.

<html>
<body>
<form method="post" action="_signin.php">
<label for="username">Username</label>
  <input type="text" name="username">
<label for="password">Password</label>
  <input type="password" name="password">
  <input type="submit" value="Sign in">
</form>
</body>
</html>

3. The form handling page

Here we have the meat of this authentication method, the PHP script that handles the form post.

<?php
  include_once("database.inc");

  $username = $_POST['username'];
  $password_sha1 = sha1($_POST['password']);

  $sql  = "SELECT username ";
  $sql .= "FROM user ";
  $sql .= "WHERE username=:u AND password_sha1=:p";
  $stmt = $pdo->prepare($sql);
  $stmt->execute(array(
            ":u"=>$username,
            ":p"=>$password_sha1
          ));
  $row = $stmt->fetch();

  // clear out any existing session that may exist
  session_start();
  session_destroy();
  session_start();

  if ($row) {
    $_SESSION['signed_in'] = true;
    $_SESSION['username'] = $username;
    header("Location: /dashboard.php");
  } else {
    $_SESSION['flash_error'] = "Invalid username or password";
    $_SESSION['signed_in'] = false;
    $_SESSION['username'] = null;
    header("Location: /index.php");
  }
?>

There are a couple important things to note here.

  • Don’t do anything with the plain-text password. Notice that the first thing this script does is create a SHA1 hash of the password. SHA1 is a popular one-way hash algorithm that takes any string of bits and creates a 40-character hash code. Once you have the hash code, it is not possible to decode it and get the original password.
  • Hashes are compared, not passwords. Also notice that this script checks to see that the SHA1 hash of what they typed and the SHA1 hash stored in the database are identical. If they are not, then we can assume their password isn’t correct. It isn’t necessary to know their actual password for this comparison.
  • Store state in the session, not in cookies. PHP maintains a cookie called PHPSESSID with the user’s browser that identifies this session. That is the only cookie you should need to deal with, and it is set in the session_start() method. When you store values in the session, it is stored on your server, not transmitted across the network. This way, it is impossible for a hijacker to pretend to be signed in. (See my notes at the end about session hijacking.)

4. Validation in the application

To handle validation on a per-page basis, I usually create a validate.inc include file that I can include on any page that requires the user to be authenticated. This script will start the session and check if the user is authenticated. If they are not, then it forwards them to the sign in form. Otherwise it allows execution to continue.

<?php
  session_start();
  if (!$_SESSION['signed_in']) {
    $_SESSION['flash_error'] = "Please sign in";
    header("Location: /index.php");
    exit; // IMPORTANT: Be sure to exit here!
  }
?>

Other considerations

Session hijacking is a possible weakness of this method. While it’s difficult, it’s not impossible to spoof the session cookie and pretend to be another user. To get around this, you can store the IP address of that the user in the session when you authenticate. Then in your validate script, check the $_SERVER[‘REMOTE_ADDR’] value with the IP address stored in the cookie.

Also consider using a salted hash instead of just a plain md5. This prevents an attacker from looking up your md5s and performing a rainbow attack.

Conclusion

Secure authentication is not complicated, but care needs to be taken at key points to ensure the privacy and security of your users.