PHP User Registration & Login Form – PDO.

This is a beginner’s tutorial on how to construct user registration and login forms using PHP’s PDO object. As it stands, there are hundreds of “PHP login tutorials” floating around on the web. Unfortunately, the vast majority of them use insecure password hashing methods and extensions that are now considered to be outdated (I’m looking at you, mysql_query).

User table structure.

I’ve kept things simple by using an extremely basic table structure. Obviously, you can customize this and add your own columns to this table if you need to (email address and name, etc). This tutorial assumes that our login is based on a username and password combination (as opposed to an email and password combination). You can import the following table structure into your database:

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(25) COLLATE utf8_unicode_ci NOT NULL,
  `password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

Note: I have added a unique index to the username column. This is because a username must be unique!

Connect.

The first order of business is to connect to MySQL using the PDO object. For a more in-depth tutorial on this, you should read my tutorial “Connecting to MySQL with PHP”. For the purpose of this tutorial, I have created a file called connect.php, which we will include throughout our scripts:

<?php

//connect.php

/**
 * This script connects to MySQL using the PDO object.
 * This can be included in web pages where a database connection is needed.
 * Customize these to match your MySQL database connection details.
 * This info should be available from within your hosting panel.
 */
 
//Our MySQL user account.
define('MYSQL_USER', 'root');
 
//Our MySQL password.
define('MYSQL_PASSWORD', '');
 
//The server that MySQL is located on.
define('MYSQL_HOST', 'localhost');

//The name of our database.
define('MYSQL_DATABASE', 'test');

/**
 * PDO options / configuration details.
 * I'm going to set the error mode to "Exceptions".
 * I'm also going to turn off emulated prepared statements.
 */
$pdoOptions = array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false
);

/**
 * Connect to MySQL and instantiate the PDO object.
 */
$pdo = new PDO(
    "mysql:host=" . MYSQL_HOST . ";dbname=" . MYSQL_DATABASE, //DSN
    MYSQL_USER, //Username
    MYSQL_PASSWORD, //Password
    $pdoOptions //Options
);

//The PDO object can now be used to query MySQL.

The code above will connect to a MySQL database using the PDO extension. You will need to read the comments that are littered throughout the code and change the MySQL connection details to match your own!

User Registration Form.

Before a user can login, he or she will need to signup to our website by using a registration form. If the registration is successful, we will insert a new user account into our users table.

<?php

//register.php

/**
 * Start the session.
 */
session_start();

/**
 * Include ircmaxell's password_compat library.
 */
require 'lib/password.php';

/**
 * Include our MySQL connection.
 */
require 'connect.php';


//If the POST var "register" exists (our submit button), then we can
//assume that the user has submitted the registration form.
if(isset($_POST['register'])){
    
    //Retrieve the field values from our registration form.
    $username = !empty($_POST['username']) ? trim($_POST['username']) : null;
    $pass = !empty($_POST['password']) ? trim($_POST['password']) : null;
    
    //TO ADD: Error checking (username characters, password length, etc).
    //Basically, you will need to add your own error checking BEFORE
    //the prepared statement is built and executed.
    
    //Now, we need to check if the supplied username already exists.
    
    //Construct the SQL statement and prepare it.
    $sql = "SELECT COUNT(username) AS num FROM users WHERE username = :username";
    $stmt = $pdo->prepare($sql);
    
    //Bind the provided username to our prepared statement.
    $stmt->bindValue(':username', $username);
    
    //Execute.
    $stmt->execute();
    
    //Fetch the row.
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    
    //If the provided username already exists - display error.
    //TO ADD - Your own method of handling this error. For example purposes,
    //I'm just going to kill the script completely, as error handling is outside
    //the scope of this tutorial.
    if($row['num'] > 0){
        die('That username already exists!');
    }
    
    //Hash the password as we do NOT want to store our passwords in plain text.
    $passwordHash = password_hash($pass, PASSWORD_BCRYPT, array("cost" => 12));
    
    //Prepare our INSERT statement.
    //Remember: We are inserting a new row into our users table.
    $sql = "INSERT INTO users (username, password) VALUES (:username, :password)";
    $stmt = $pdo->prepare($sql);
    
    //Bind our variables.
    $stmt->bindValue(':username', $username);
    $stmt->bindValue(':password', $passwordHash);

    //Execute the statement and insert the new account.
    $result = $stmt->execute();
    
    //If the signup process is successful.
    if($result){
        //What you do here is up to you!
        echo 'Thank you for registering with our website.';
    }
    
}

?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Register</title>
    </head>
    <body>
        <h1>Register</h1>
        <form action="register.php" method="post">
            <label for="username">Username</label>
            <input type="text" id="username" name="username"><br>
            <label for="password">Password</label>
            <input type="text" id="password" name="password"><br>
            <input type="submit" name="register" value="Register"></button>
        </form>
    </body>
</html>

A few things to note:

  • We are using ircmaxell’s password_compat library. This will work for PHP version 5.3.7 and above. If you are lucky enough to be using a PHP version that is 5.5 or above, then you’ll be happy to know that the password_hash function is already built-in.
  • We are using a respected password hashing algorithm called BCRYPT. Other login tutorials make the mistake of promoting hashing algorithms such as md5 and sha1. The problem with md5 and sha1 is that they are “too fast”, which basically means that password crackers can “break them” at a much quicker rate.
  • You will need to add your own error checking to this registration form (username length, what type of characters are allowed, etc). You will also need to implement your own method of handling user errors as the code above is pretty basic in that respect. For further help on this subject, be sure to check out my tutorial on handling form errors in PHP.
  • If the insert code above looks completely foreign to you, then you should probably check out my article on Inserts with PDO.

User Login with PHP.

A basic example of a website login w/ PHP and the PDO object:

<?php

//login.php

/**
 * Start the session.
 */
session_start();

/**
 * Include ircmaxell's password_compat library.
 */
require 'lib/password.php';

/**
 * Include our MySQL connection.
 */
require 'connect.php';


//If the POST var "login" exists (our submit button), then we can
//assume that the user has submitted the login form.
if(isset($_POST['login'])){
    
    //Retrieve the field values from our login form.
    $username = !empty($_POST['username']) ? trim($_POST['username']) : null;
    $passwordAttempt = !empty($_POST['password']) ? trim($_POST['password']) : null;
    
    //Retrieve the user account information for the given username.
    $sql = "SELECT id, username, password FROM users WHERE username = :username";
    $stmt = $pdo->prepare($sql);
    
    //Bind value.
    $stmt->bindValue(':username', $username);
    
    //Execute.
    $stmt->execute();
    
    //Fetch row.
    $user = $stmt->fetch(PDO::FETCH_ASSOC);
    
    //If $row is FALSE.
    if($user === false){
        //Could not find a user with that username!
        //PS: You might want to handle this error in a more user-friendly manner!
        die('Incorrect username / password combination!');
    } else{
        //User account found. Check to see if the given password matches the
        //password hash that we stored in our users table.
        
        //Compare the passwords.
        $validPassword = password_verify($passwordAttempt, $user['password']);
        
        //If $validPassword is TRUE, the login has been successful.
        if($validPassword){
            
            //Provide the user with a login session.
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['logged_in'] = time();
            
            //Redirect to our protected page, which we called home.php
            header('Location: home.php');
            exit;
            
        } else{
            //$validPassword was FALSE. Passwords do not match.
            die('Incorrect username / password combination!');
        }
    }
    
}
 
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Login</title>
    </head>
    <body>
        <h1>Login</h1>
        <form action="login.php" method="post">
            <label for="username">Username</label>
            <input type="text" id="username" name="username"><br>
            <label for="password">Password</label>
            <input type="text" id="password" name="password"><br>
            <input type="submit" name="login" value="Login">
        </form>
    </body>
</html>

Step by step explanation of the code above:

  1. We start the session by using the function session_start. This function MUST be called on every page.
  2. We require the password_compat library.
  3. We require our connect.php file, with connects to MySQL and instantiates the PDO object.
  4. If the POST variable “login” exists, we assume that the user is attempting to login to our website.
  5. We grab the field values from our login form.
  6. Using the username that we were supplied with, we attempt to retrieve the relevant user from our MySQL table. We do this by using a prepared SELECT statement.
  7. If a user with that username exists, we compare the two passwords by using the function password_verify (this takes care of the hash comparison for you).
  8. If the password hashes match, we supply the user with a login session. We do this by creating two session variables called “user_id” and “logged_in”.
  9. We then redirect the user to home.php, which is our login-protected page.

Note: You will need to implement your own way of dealing with user errors. In this tutorial, I am using the die statement, which is a bit nasty.

Protected page.

Our protected page is called home.php. I’ve kept it simple:

<?php

//home.php

/**
 * Start the session.
 */
session_start();


/**
 * Check if the user is logged in.
 */
if(!isset($_SESSION['user_id']) || !isset($_SESSION['logged_in'])){
    //User not logged in. Redirect them back to the login.php page.
    header('Location: login.php');
    exit;
}


/**
 * Print out something that only logged in users can see.
 */

echo 'Congratulations! You are logged in!';

Step-by-step:

  • I start the session by using session_start. This is important, as our login system will not work without a valid user session.
  • We check to see if the user has the required session variables (user ID and login timestamp). If the user does not have either of these session variables, we simply redirect them back to the login.php page. Obviously, you can customize this to suit your own needs.
  • We print out a test message, just to show that the login system is functioning as expected.

If you’re new to all of this, then you should probably download the code examples below and implement the login system on your development / local machine. Make sure that you read the comments that are contained in the code! If you have any questions or recommendations, be sure to post a comment below!