User Authentication

  1. Overview
    1. User authentication is the process of verifying that a user is who he/she claims to be
    2. Many websites allow users to create an account or profile, which requires user authentication so the user can use the account
    3. Most common user authentication method: User supplies a username that uniquely identifies the user and a password that supposedly only the user knows
    4. Although this authentication method is very problematic and many smart people are working on better methods, this is the type of authentication we will implement
    5. Apache allows you to create .htaccess files to limit who has access to files in a particular directory, but we will not use this technique here
  2. Password hashing
    1. A website that implements username/password authentication needs to store both username and password in a database
    2. When the user attempts to logs-in, the supplied username and password are checked to see if they match what is in the database
    3. What not to do
      1. A naive approach is to store both usernames and passwords in plain text
        +----------+------------+
        | username | password   |
        +----------+------------+
        | bsmith   | abc123     |
        | jwhite   | opensesame |
        | sblack   | qwerty     |
        +----------+------------+
        
      2. Problem 1: If a hacker gets access to the database (most likely SQL injection), all passwords are compromised
      3. Problem 2: Developers also have access to all passwords, which can be tempting to misuse
    4. Hashing function
      1. Better solution is to use a hashing function to store a hash of the password
      2. A hashing function converts a piece of data of any size into a short, unique, fixed-length string called a hash
                             +-----------+
                             |  Hashing  |
        This is my text. --> |    func   | --> b10a8db164e07
                             |           |
                             +-----------+
        
      3. Properties of a good hashing function
        1. Produces a hash that cannot be reversed (impossible to convert b10a8db164e07 back into "This is my text.")
        2. Collision resistant: Low likelihood of producing the same hash for two different inputs
      4. Popular hashing functions:
        1. MD5 - produces a 16-byte hash value but is currently not recommended
          mysql> SELECT md5('This is my text.');
          +----------------------------------+
          | md5('This is my text.')          |
          +----------------------------------+
          | e7e233625b80ffb4192369528e02994a |
          +----------------------------------+
          
        2. SHA-1 - very popular function with a theoretical weakness that produces a 20 byte hash
          mysql> SELECT sha1('This is my text.');
          +------------------------------------------+
          | sha1('This is my text.')                 |
          +------------------------------------------+
          | 00a863791c859442d15d2dbd5a68b93dfc21abcf |
          +------------------------------------------+
          
        3. SHA-2 functions - more secure functions that produce different size hashes: SHA-224 = 24 bytes, SHA-256 = 32 bytes, SHA-384 = 48 bytes, SHA-512 = 64 bytes (not supported in MySQL 5.0 - upgrade required)
        4. bcrypt - Incorporates salt to protect against rainbow attacks and uses an iteration count that can be increased to make it slower
    5. Increasing security with salt
      1. Password cracking is the process of recovering passwords from data, like the database of a compromised website
      2. If database is stolen, a dictionary attack could be used to see if any of the hashes match the hashes of words from a dictionary or the Web
      3. Rainbow tables enable a more sophisticated attack
      4. These attacks can be mitigated by using salt
        1. Salt is a string of random bits added to a password to create a hash
          define('SALT_LENGTH', 10);
          
          // Generates a string that looks like this: d7a5b6143d
          $salt = substr(md5(uniqid(rand(), true)), 0, SALT_LENGTH);   
          
        2. Prepend the salt to the password and store it in the database along with the password
          // Store $salted_hash and $salt in the database
          $salted_hash = md5($salt . $password);  
          
        3. Since the salt is unique to each user, there will never be two users with identical passwords that have the same password hash
        4. For a large database, a hacker would have to create a rainbow table using each user's salt value, which is impractical
      5. bcrypt stores the salt as part of the hash
  3. Storing password hash
    1. Instead of storing the plain text password, store the password's hash (example is using bcrypt)
      +----------+--------------------------------------------------------------+
      | username | password                                                     |
      +----------+--------------------------------------------------------------+
      | bsmith   | $2y$10$HjaogAYhazNAPqeLb57tNeGpQmvYLu1Pg8y8GdyULi6fKLMN/1Pum |
      | jwhite   | $2y$10$hONd1dWW2.Bk/9P8SCTHg.fVFH.gJHVYM6dipYIUUuAZNGLiZwXea |
      | sblack   | $2y$10$i8fsnV3xLNXJawIjJLPZP.J9b4F6pumVCcfaoqfZoHx2875xhZlf. |
      +----------+--------------------------------------------------------------+
      
    2. Use PHP password_hash(password, hashFunction) function to produce a bcrypt hash of the password for inserting into the database
      // Get user data
      $username = $mysqli->real_escape_string($_POST['username']);
      $password = $_POST['password'];
      $name = $mysqli->real_escape_string($_POST['name']);
      
      // Create bcrypt hash of the password
      $password_hash = password_hash($password, PASSWORD_BCRYPT);
      
      // Insert new user into the database
      $cmd = "INSERT INTO Users VALUES ('$username', '$password_hash', '$name', '', NULL)";
      
    3. When user provides their username and password, use password_verify(password, hash) to see if the bcrypt hash of their password matches the hash in the database
      // Get the submitted username and password
      $username = $mysqli->real_escape_string($_POST['username']);
      $password = $_POST['password'];
      
      // Get the password hash for this username
      $sql = "SELECT username, password FROM Users WHERE username='$username'";
      $result = $mysqli->query($sql) or
          die("Error executing query: ($mysqli->errno) $mysqli->error<br>SQL = $sql");
      
      if ($result->num_rows == 0)
      	echo "Sorry, but that username could not be found.";
      else
      {
      	$row = $result->fetch_assoc();
      	
      	// See if bcrypt hash of submitted password matches the database 
      	if (password_verify($password, $row['password']))
      		echo "Welcome, row[$username]!";
      	else
      		echo "Sorry, the password is not correct.";
      }
      
  4. Granting access
    1. Protected pages should only be displayed to authenticated users
    2. Set a session variable to indicate that a user has successfully authenticated
      // Code from login.php
      if (password_verify($password, $row['password']))
      {
      	$_SESSION['login'] = 'true';
      	echo "Welcome, $username!<br>";
      	echo "<a href='protected.php'>Protected Page</a>";
      }
      else
      	echo "Sorry, the username/password is not correct.";
      
    3. In protected pages redirect the browser back to the login screen if the session variable is not set
      // In protected.php
      session_start(); 
      
      // Redirect the browser unless user has authenticated
      if (!isset($_SESSION['login']))
      {
      	header("Location: login.php");	
      	exit;
      }
      
    4. When the user logs out, delete the login session variable
      // In logout.php
      session_start(); 
      
      // Delete all session variables
      session_destroy(); 
      
      // Redirect back to the login screen
      header("Location: login.php");	
      
  5. Improving security
    1. Enforcing strong passwords - passwords that are long, mixed-case, digits, don't contain dictionary words
    2. Use HTTPS to encrypt http traffic
      1. If someone is watching the network traffic with a packet analyzer, they won't see the password submitted from the form in plain text
      2. The session ID cannot be hijacked since it will also be encrypted
    3. Only allow a limited number of failed login attempts before locking the account or asking security questions
    4. Force users to answer security questions when creating an account that can be asked when the user wants to reset a password
    5. Use a third party to authenticate, typically using OpenID