Login/Register module is one of those features almost all sites have nowadays. But develop a secure login and registration form can be a nightmare for PHP beginners. Because various steps and validation are involved in building both forms.
So in this tutorial, we are going to make login/register system using PHP PDO. If you have no or little knowledge of PDO, you can read my previous tutorial on PDO basics and PDO prepared statement. For styling I will be using Bootstrap 4. Source code will consist of 5 files which would be:
- Config.php: File with pdo connection
- Login.php: File having login form html and server side validation and login code
- Register.php: File having register form html, server side validation and register code
- Dashboard.php: User will land on this file after successful login
- Logout.php: File having logout code
So let’s start now. First of all, we need to create a database and table. I will be creating a database as a demo and MySQL table name will be members.
Create Database:
1 2 3 |
Create Database demo; |
Create Table:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
CREATE TABLE `members` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `first_name` VARCHAR(255) NOT NULL DEFAULT '0', `last_name` VARCHAR(255) NOT NULL DEFAULT '0', `email` VARCHAR(255) NOT NULL DEFAULT '0', `password` VARCHAR(255) NOT NULL DEFAULT '0', `created_at` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', `updated_at` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`) ) COLLATE='latin1_swedish_ci' ENGINE=InnoDB AUTO_INCREMENT=7 ; |
Config.php:
First of all we need to create a MYSQL database connection. PDO class constructor takes 3 parameters which are database source (dsn), db user and db password. I have created a separate variable for those which you can see below. Then I make connection using $pdo
variable and put connection in try and catch block. $pdo->setAttribute()
method set an attribute on the database handle. This time I will throw an exception. If you wish to read more about setAttribute
method and its argument you can visit PDO::setAttribute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php $dsn = 'mysql:dbname=demo;host=localhost'; $user = 'root'; $password = ''; try { $pdo = new PDO($dsn,$user,$password); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $e) { echo "PDO error".$e->getMessage(); die(); } ?> |
Login.php:
Login file consist of two parts. One is HTML part and second is PHP part. Let’s discuss HTML part first. As I said earlier that I will be using bootstrap 4 for form desiging.
First I add the CSS CDN link in head section so that I can access bootstrap default styling classes. I also add php code to print server side validation errors. $errors
is an array of errors.
In PHP part (which is on top), first I call session_start()
function which is necessary to start new session. After that I include the config.php
file to access the database. Then there is a if condition which make sure that form will submit. Under same condition there is another condition that ensure that email and password would not empty. After getting email and password, I am validating email and if email is valid then I will check user associated with that email or not. If email is found then I match the password using password_verify()
function and if the password is correct I will create a session and redirect user to dashboard.php
page.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
<?php session_start(); require_once('config.php'); if(isset($_POST['submit'])) { if(isset($_POST['email'],$_POST['password']) && !empty($_POST['email']) && !empty($_POST['password'])) { $email = trim($_POST['email']); $password = trim($_POST['password']); if(filter_var($email, FILTER_VALIDATE_EMAIL)) { $sql = "select * from members where email = :email "; $handle = $pdo->prepare($sql); $params = ['email'=>$email]; $handle->execute($params); if($handle->rowCount() > 0) { $getRow = $handle->fetch(PDO::FETCH_ASSOC); if(password_verify($password, $getRow['password'])) { unset($getRow['password']); $_SESSION = $getRow; header('location:dashboard.php'); exit(); } else { $errors[] = "Wrong Email or Password"; } } else { $errors[] = "Wrong Email or Password"; } } else { $errors[] = "Email address is not valid"; } } else { $errors[] = "Email and Password are required"; } } ?> <!doctype html> <html> <head> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> </head> <body class="bg-dark"> <div class="container h-100"> <div class="row h-100 mt-5 justify-content-center align-items-center"> <div class="col-md-5 mt-5 pt-2 pb-5 align-self-center border bg-light"> <h1 class="mx-auto w-25" >Login</h1> <?php if(isset($errors) && count($errors) > 0) { foreach($errors as $error_msg) { echo '<div class="alert alert-danger">'.$error_msg.'</div>'; } } ?> <form method="POST" action="<?php echo $_SERVER['PHP_SELF'];?>"> <div class="form-group"> <label for="email">Email:</label> <input type="text" name="email" placeholder="Enter Email" class="form-control"> </div> <div class="form-group"> <label for="email">Password:</label> <input type="password" name="password" placeholder="Enter Password" class="form-control"> </div> <button type="submit" name="submit" class="btn btn-primary">Submit</button> <a href="register.php" class="btn btn-primary">Register</a> </form> </div> </div> </div> </body> </html> |
Register.php:
Register file is also consist of two parts HTML and PHP. In HTML part, styling is same like login form. $errors
is also there for printing server side errors. I also add $success
variable to print successful form submission message. Also each input field has conditional value having ?? (double question mark). ?? (Null coalescing operator) is a short hand of ternary operator and it was added in php 7.0.
In PHP part, I am using session_start
and require_once
functions and then I add a if clause to check form will submit or not. If form will submit then I will make sure that every input field must have value. If any of the input field doesn’t come with value then I will show validation error. If all fields come with value then first I will check email is valid or not. If the email is valid, I will check in the database that email should not be present in database. Otherwise I will through email already exist error. If email will unique, I will create a user and print successful message.
One more thing, I am using password_hash()
function to create hash password. I already wrote a tutorial on password hash in one of my previous blog post.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
<?php session_start(); require_once('config.php'); if(isset($_POST['submit'])) { if(isset($_POST['first_name'],$_POST['last_name'],$_POST['email'],$_POST['password']) && !empty($_POST['first_name']) && !empty($_POST['last_name']) && !empty($_POST['email']) && !empty($_POST['password'])) { $firstName = trim($_POST['first_name']); $lastName = trim($_POST['last_name']); $email = trim($_POST['email']); $password = trim($_POST['password']); $options = array("cost"=>4); $hashPassword = password_hash($password,PASSWORD_BCRYPT,$options); $date = date('Y-m-d H:i:s'); if(filter_var($email, FILTER_VALIDATE_EMAIL)) { $sql = 'select * from members where email = :email'; $stmt = $pdo->prepare($sql); $p = ['email'=>$email]; $stmt->execute($p); if($stmt->rowCount() == 0) { $sql = "insert into members (first_name, last_name, email, `password`, created_at,updated_at) values(:fname,:lname,:email,:pass,:created_at,:updated_at)"; try{ $handle = $pdo->prepare($sql); $params = [ ':fname'=>$firstName, ':lname'=>$lastName, ':email'=>$email, ':pass'=>$hashPassword, ':created_at'=>$date, ':updated_at'=>$date ]; $handle->execute($params); $success = 'User has been created successfully'; } catch(PDOException $e){ $errors[] = $e->getMessage(); } } else { $valFirstName = $firstName; $valLastName = $lastName; $valEmail = ''; $valPassword = $password; $errors[] = 'Email address already registered'; } } else { $errors[] = "Email address is not valid"; } } else { if(!isset($_POST['first_name']) || empty($_POST['first_name'])) { $errors[] = 'First name is required'; } else { $valFirstName = $_POST['first_name']; } if(!isset($_POST['last_name']) || empty($_POST['last_name'])) { $errors[] = 'Last name is required'; } else { $valLastName = $_POST['last_name']; } if(!isset($_POST['email']) || empty($_POST['email'])) { $errors[] = 'Email is required'; } else { $valEmail = $_POST['email']; } if(!isset($_POST['password']) || empty($_POST['password'])) { $errors[] = 'Password is required'; } else { $valPassword = $_POST['password']; } } } ?> <!doctype html> <html> <head> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> </head> <body class="bg-dark"> <div class="container h-100"> <div class="row h-100 mt-5 justify-content-center align-items-center"> <div class="col-md-5 mt-3 pt-2 pb-5 align-self-center border bg-light"> <h1 class="mx-auto w-25" >Register</h1> <?php if(isset($errors) && count($errors) > 0) { foreach($errors as $error_msg) { echo '<div class="alert alert-danger">'.$error_msg.'</div>'; } } if(isset($success)) { echo '<div class="alert alert-success">'.$success.'</div>'; } ?> <form method="POST" action="<?php echo $_SERVER['PHP_SELF'];?>"> <div class="form-group"> <label for="email">First Name:</label> <input type="text" name="first_name" placeholder="Enter First Name" class="form-control" value="<?php echo ($valFirstName??'')?>"> </div> <div class="form-group"> <label for="email">Last Name:</label> <input type="text" name="last_name" placeholder="Enter Last Name" class="form-control" value="<?php echo ($valLastName??'')?>"> </div> <div class="form-group"> <label for="email">Email:</label> <input type="text" name="email" placeholder="Enter Email" class="form-control" value="<?php echo ($valEmail??'')?>"> </div> <div class="form-group"> <label for="email">Password:</label> <input type="password" name="password" placeholder="Enter Password" class="form-control" value="<?php echo ($valPassword??'')?>"> </div> <button type="submit" name="submit" class="btn btn-primary">Submit</button> <p class="pt-2"> Back to <a href="login.php">Login</a></p> </form> </div> </div> </div> </body> </html> |
Dashboard.php:
After successful login, User will land on this page. This page has session_start function and I am also checking that dashboard.php should not access directly. HTML code is just a heading and a logout link.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php session_start(); if(!$_SESSION['id']){ header('location:login.php'); } ?> <h1>Welcome <?php echo ucfirst($_SESSION['first_name']); ?></h1> <a href="logout.php?logout=true">Logout</a> |
Logout.php:
This file destroy user session and redirect user to login.page
1 2 3 4 5 6 7 8 9 10 |
<?php session_start(); if(isset($_SESSION)){ session_destroy(); header('location:login.php'); exit(); } ?> |