A Simple Login Class Using PHP and MySQL
Login System is always required for any web application that is dynamic. So, this is what actually happened to me. Every website I created, require some form of login system. So, I usually integrate a login system. The problem is that when I update some security features, I've to re-code for all those website. This unique problem, leads me to the creation of this tutorial inspired by the Hashing Security blog.
The idea here is to separate the login system from the rest of the application. The best way to implement this is to use a Login Class and simply integrate the class to any application you design. In this tutorial, I would like to make the login system as secure as possible, hence I would like to ensure a strong encryption of passwords. Now instead of designing my own encryption technique, I'll use a strong and reliable technique described by Crack Station - a Hashing Security blog. Hence before you start download this PasswordHash.php file from GitHub.
This Tutorial will be in three parts:
- Designing the Database
- Creating the Login Class
- Implementing the Class.
Designing the Database: Let's create a simple database containing the following fields:
In the above structure, we have a userid, username, password, useremail and userlevel. You can create the database table using any tools you like. My choice is phpMyAdmin, or you can simply copy this SQL statement and use it directly.
CREATE TABLE IF NOT EXISTS `userlogin` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL DEFAULT '',
`password` varchar(100) NOT NULL DEFAULT '',
`useremail` varchar(50) NOT NULL DEFAULT '',
`userlevel` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (userid)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
The userid is also set as the primary key. You can use the utf8 CHARSET too if you want. Let us take some time to understand this basic SQL table. The username and user email are two different field. Hence, a user will need a username to login. You can also set that username should be UNIQUE or combine both username and useremail as UNIQUE fields. The userlevel field is used in case where your website users are of different levels. Example:
- Administrator | Level - 0
- Editor | Level - 1
- Subscriber | Level - 2
Thus, you can have different sets of user level in your website.
Finally, create a configuration file (let's name it db-config.php). In this file, database login information is defined as follows:
<?php //Define the Databse Login information
define("DB_HOST", "localhost"); //Your MySQL hostname
define("DB_USER", "root"); //Replace with your MySQL User
define("DB_PASSWORD", "password"); //Replace with your MySQL Password
define("DB_NAME", "myapp"); //Replace with your MySQL Database
Save this file in your application folder - preferably set an include folder and save there. So, with this, we have come to the conclusion of the first part of this tutorial.
Creating the Login Class: The second part of this tutorial is to create the login class to use with our application. Now, I would remind you that I'll be using an encryption technique which is strong and secure as described by Crack Station - a Hashing Security blog. Hence before you start make sure you've download this PasswordHash.php file from GitHub save the file in the same include folder that you've saved the db-config.php file.
Now, create a file and let's name it loginClass.php. Here, we will place all our login system. The empty file will look something like this:
<?php
/*
* simpleLogin class using Password Hashing With PBKDF2
* by (https://defuse.ca/php-pbkdf2.htm)
* Copyright (c) 2015 Farlando Diengdoh
*
*/
//Start session
session_start();
//set error reporting to full when working local
//error_reporting(0); //for a live site
error_reporting( E_STRICT | E_ALL );
//Include the config file
require_once("db-config.php");
//Now include the PasswordHashClass.php file
require_once("PasswordHashClass.php");
//Start Class Login
class loginClass{
//All your loginClass code goes here
}
?>
In the above file, we have started a session, then set the error reporting and created an empty class loginClass. Before we go ahead, let us simplify the process of accessing our database. Here, we'll implement PDO prepared statement. So, we'll extends the PDO class and make things simpler. We'll modify our loginClass.php file by adding another class just before our loginClass as follows:
<?php
prepare( $statement );
$exec = $stmt->execute( $params );
if ( $exec ) {
return $stmt->fetchAll( $fetch_style );
} else {
return FALSE;
}
}
}
//Start Class Login
class loginClass{
//All your loginClass code goes here
The above extended class will allow for simpler coding in our loginClass. Next we will see how to create the functions which will help in building our application.
So, you've seen about how to setup a database. Next, in the loginClass we'll build the different methods that will be used in any application as a login system. Firstly, we'll set some private variables. In Objected Oriented Programming, private variables are accessible only by the methods of the class and not from outside. Some, important variables can be set as private here:
<?php
//Database information
private $db_host = DB_HOST;
private $db_user = DB_USER;
private $db_pass = DB_PASSWORD;
private $db_name = DB_NAME;
//Table Structure
private $table_name = 'userlogin'; //Table name;
private $id_col = 'userid'; //User ID Field
private $user_col = 'username'; //Username Field
private $password_col = 'password'; //Password Field
private $email_col = 'useremail'; //Email Field
private $level_col = 'userlevel'; //User Level Field
//message array
private $messages = array();
private $errormsg = false;
Here, we've set the database information and the structure of the tables and some messages variables. The next part is to create public functions to make the class functioning. The functions (methods) that we'll implement are the following:
<?php
//method to check if logged in
public function isUserLoggedIn(){
}
//method to login users
public function login( $usrname, $password ){
}
//method to register new user
public function registerUser( $username, $useremail, $password ){
}
//method to logout
public function logout(){
}
These are four important functions required for a login system, we can extend the system further, but as of now we'll first take a look at these four functions. So, let's break down these four functions one by one.
The function isUserLoggedIn() check if a user has already logged in. This is possible through session variables that we will set every time a user login. The login() function will allow for logging in a user, set important session variables. The registerUser() is understandable function to register new users into the system. The last function is simply used to logout any active user and destroy/unset session variables. So, here are the codes for the different functions. Firstly, the login() function:
<?php
//method to login users
public function login( $username, $password ){
//connect to Database
$DB = new MyDB("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_pass);
//Check Database if the username exist
$result = $DB->pdoQuery("SELECT * FROM " . $this->table_name . " WHERE " . $this->user_col . " = ?", array( $username ) );
//if Not empty user exist
if( !empty( $result ) ){
//if user exist, check password match
$user =& $result[0];
if (PasswordHash::validate_password($password, $user[$this->password_col])) {
//Prevent reuse of same session id by regenerating at every login
session_regenerate_id();
//register sessions
$_SESSION['loggedin'] = 1;
//USER ID
$_SESSION['userid'] = $user['userid'];
//USERName session
$_SESSION['username'] = $user[$this->user_col];
//userlevel session is optional. Use it if you have different user levels
$_SESSION['userlevel'] = $user[$this->level_col];
//useremail session
$_SESSION['useremail'] = $user[$this->email_col];
//Login Successful Send Message
$response = array ( 'message' => 'success', 'msgbody' => 'Login Successfull' );
}else{
//Login Not Successful (Password Error) Send generic error
$response = array ( 'message' => 'error', 'msgbody' => 'Invalid Credentials' );
}
}else{
// destroy session
session_destroy();
//Login not successful (Username not exist) Send generic error
$response = array ( 'message' => 'error', 'msgbody' => 'Invalid Credentials' );
}
//we return the response array
return $response;
}
Here, basically the function check first if username exist, if the username exist, check for password match, if input password match the saved password, then login the user. After a user login, session variables are registered. You can include as many as your application require. Here, I've kept five session variables that store userid, username, userlevel, useremail and a loggedin state. If the password didn't match, for security reasons send a generic message as Invalid Credentials rather than saying Invalid Password. Also, if the user don't exist, again send a generic message Invalid Credentials rather than User Doesn't exist. This, will prevent hackers from guessing the right username that exist.
Next, we'll look at the isUserLoggedIn(). This function is quite simple - all it does is check if the SESSION['loggedin'] is set or not. If it is set and value is true, then return positive. So, an application just check this value and if it exist, display the secure part of the application - we'll talk about this part later in our implementation of this class.
<?php
//method to check if logged in
public function isUserLoggedIn(){
if (isset($_SESSION['loggedin']) AND $_SESSION['loggedin'] == true) {
return true;
}
// default return
return false;
}
The next part is the function to register new users. In this function, the username, useremail are first check if they are already exist in the database. If either username or useremail is already registered, then the registration process is stopped. If everything is in order, the password is hashed first using the PasswordHash class. The hashed password, username and email is then stored into the database. The function will look something like this.
<?php
//method to register new user
public function registerUser( $username, $useremail, $password ){
//create connection
$DB = new MyDB("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_pass);
//Check if username already exist
$check_user = $DB->pdoQuery("SELECT * FROM " . $this->table_name . " WHERE " . $this->user_col . " = ?", array( $username ) );
if( !empty( $check_user ) ){
$response = array ( 'message' => 'error', 'msgbody' => 'Username already in use.' );
return $response;
eixit(0);
}
//Check if email already exist
$check_email = $DB->pdoQuery("SELECT * FROM " . $this->table_name . " WHERE " . $this->email_col . " = ?", array( $useremail ) );
if( !empty( $check_email ) ) {
$response = array ( 'message' => 'error', 'msgbody' => 'Email already registered.' );
return $response;
eixit(0);
}
//hash the password
$hashed = PasswordHash::create_hash( $password );
$con = new PDO("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_pass);
$stmt = $con->prepare(
"INSERT INTO " . $this->table_name . "
(`" . $this->user_col . "`,
`" . $this->password_col . "`,
`" . $this->email_col . "`,
`" . $this->level_col . "`)
VALUES ( ?, ?, ?, ? );");
$params = array($username, $hashed, $useremail, 1);
$res = $stmt->execute( $params );
if( $res ){
$response = array ( 'message' => 'success', 'msgbody' => 'User Registered Successfully.' );
}else{
$response = array ( 'message' => 'error', 'msgbody' => 'Error Registering User.' );
}
//return the message
return $response;
}
Finally, the last function is to logout an active user. Here, we will deregister the session using unset. If your application doesn't need any use other session data, then you can destroy the session or to be safe unset is the best practice. Now our logout function will look like this:
<?php
//method to logout
public function logout(){
//deregister sessions
unset( $_SESSION['loggedin'] );
unset( $_SESSION['userid'] );
unset( $_SESSION['username'] );
unset( $_SESSION['userlevel'] );
unset( $_SESSION['useremail'] );
}
In order to secure our application, we'll implement a data sanitization. For example an email must be a valid email address. We will also limit the characters that can work with a username - say we'll allow only alphanumeric (aA to zZ and 0-9) with the underscore character "_". Now to do that, we'll create two functions that can handle this part. PHP from version 5 and above has some filters to validate and also to sanitize. Validation is required to check if the email/username is within our allowed characters and format. While sanitization is to prevent SQL injection. The two functions can be written as follows:
<?php
//Validate and sanitize email before storing
public function validateEmail( $str ){
if(filter_var( $str, FILTER_VALIDATE_EMAIL ) ){
return filter_var( $str, FILTER_SANITIZE_EMAIL );
}else{
return FALSE;
}
}
//validate username before storing.
public function validateUser( $str ){
/*
* This /^[A-Za-z][A-Za-z0-9]{3,}(?:_[A-Za-z0-9]+)*$/ expression
* allows a username to start with alphabet
* allows numbers
* allows undersore in between not at begining or end.
*/
$opt = array( "options"=>array( "regexp" => "/^[A-Za-z][A-Za-z0-9]{3,}(?:_[A-Za-z0-9]+)*$/" ) );
return filter_var( $str, FILTER_VALIDATE_REGEXP, $opt );
}
Our completed loginClass.php file now looks like the following (You can double click on the code area to copy the whole class and function):
<?php
/*
* simpleLogin class using Password Hashing With PBKDF2
* by (https://defuse.ca/php-pbkdf2.htm)
* Copyright (c) 2015 Farlando Diengdoh
*
*/
//Start session
session_start();
//set error reporting to full when working local
//error_reporting(0); //for a live site
error_reporting( E_STRICT | E_ALL );
//Include the config file
require_once("db-config.php");
//Now include the PasswordHashClass.php file
require_once("PasswordHashClass.php");
//Use Prepared Statement
class MyDB extends PDO{
public function pdoQuery( $statement, $params = array(), $fetch_style = PDO::FETCH_ASSOC ){
$stmt = $this->prepare( $statement );
$exec = $stmt->execute( $params );
if ( $exec ) {
return $stmt->fetchAll( $fetch_style );
} else {
return FALSE;
}
}
}
//Start Class Login
class loginClass{
//All your loginClass code goes here
//Database information
private $db_host = DB_HOST;
private $db_user = DB_USER;
private $db_pass = DB_PASSWORD;
private $db_name = DB_NAME;
//Table Structure
private $table_name = 'userlogin'; //Table name;
private $id_col = 'userid'; //User ID Field
private $user_col = 'username'; //Username Field
private $password_col = 'password'; //Password Field
private $email_col = 'useremail'; //Email Field
private $level_col = 'userlevel'; //User Level Field
//message array
private $messages = array();
private $error = false;
//method to check if logged in
public function isUserLoggedIn(){
if (isset($_SESSION['loggedin']) AND $_SESSION['loggedin'] == true) {
return true;
}
// default return
return false;
}
//Validate and sanitize email before storing
public function validateEmail( $str ){
if(filter_var( $str, FILTER_VALIDATE_EMAIL ) ){
return filter_var( $str, FILTER_SANITIZE_EMAIL );
}else{
return FALSE;
}
}
//validate username before storing.
public function validateUser( $str ){
/*
* This /^[A-Za-z][A-Za-z0-9]{3,}(?:_[A-Za-z0-9]+)*$/ expression
* allows a username to start with alphabet
* allows numbers
* allows undersore in between not at begining or end.
*/
$opt = array( "options"=>array( "regexp" => "/^[A-Za-z][A-Za-z0-9]{3,}(?:_[A-Za-z0-9]+)*$/" ) );
return filter_var( $str, FILTER_VALIDATE_REGEXP, $opt );
}
//method to login users
public function login( $username, $password ){
//connect to Database
$DB = new MyDB("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_pass);
//Check Database if the username exist
$result = $DB->pdoQuery("SELECT * FROM " . $this->table_name . " WHERE " . $this->user_col . " = ?", array( $username ) );
//if Not empty user exist
if( !empty( $result ) ){
//if user exist, check password match
$user =& $result[0];
if (PasswordHash::validate_password($password, $user[$this->password_col])) {
//Prevent reuse of same session id by regenerating at every login
session_regenerate_id();
//register sessions
$_SESSION['loggedin'] = 1;
//USER ID
$_SESSION['userid'] = $user['userid'];
//USERName session
$_SESSION['username'] = $user[$this->user_col];
//userlevel session is optional. Use it if you have different user levels
$_SESSION['userlevel'] = $user[$this->level_col];
//useremail session
$_SESSION['useremail'] = $user[$this->email_col];
//Login Successful Send Message
$response = array ( 'message' => 'success', 'msgbody' => 'Login Successfull' );
}else{
//Login Not Successful (Password Error) Send generic error
$response = array ( 'message' => 'error', 'msgbody' => 'Invalid Credentials' );
}
}else{
// destroy session
session_destroy();
//Login not successful (Username not exist) Send generic error
$response = array ( 'message' => 'error', 'msgbody' => 'Invalid Credentials' );
}
//we return the response array
return $response;
}
//method to register new user
public function registerUser( $username, $useremail, $password ){
//create connection
$DB = new MyDB("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_pass);
//Check if username already exist
$check_user = $DB->pdoQuery("SELECT * FROM " . $this->table_name . " WHERE " . $this->user_col . " = ?", array( $username ) );
if( !empty( $check_user ) ){
$response = array ( 'message' => 'error', 'msgbody' => 'Username already in use.' );
return $response;
eixit(0);
}
//Check if email already exist
$check_email = $DB->pdoQuery("SELECT * FROM " . $this->table_name . " WHERE " . $this->email_col . " = ?", array( $useremail ) );
if( !empty( $check_email ) ) {
$response = array ( 'message' => 'error', 'msgbody' => 'Email already registered.' );
return $response;
eixit(0);
}
//hash the password
$hashed = PasswordHash::create_hash( $password );
$con = new PDO("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_pass);
$stmt = $con->prepare(
"INSERT INTO " . $this->table_name . "
(`" . $this->user_col . "`,
`" . $this->password_col . "`,
`" . $this->email_col . "`,
`" . $this->level_col . "`)
VALUES ( ?, ?, ?, ? );");
$params = array($username, $hashed, $useremail, 1);
$res = $stmt->execute( $params );
if( $res ){
$response = array ( 'message' => 'success', 'msgbody' => 'User Registered Successfully.' );
}else{
$response = array ( 'message' => 'error', 'msgbody' => 'Error Registering User.' );
}
//return the message
return $response;
}
//method to logout
public function logout(){
//deregister sessions
unset( $_SESSION['loggedin'] );
unset( $_SESSION['userid'] );
unset( $_SESSION['username'] );
unset( $_SESSION['userlevel'] );
unset( $_SESSION['useremail'] );
}
}
The Final Part of this tutorial is how to implement the loginClass that we've created earlier. Before we go ahead, make sure the following files are present in your folder. My folder arrangement is as follows:
|-localhost
| |- includes/
| | |- db-config.php
| | |- loginClass.php
| | |- PasswordHashClass.php
|- |- index.php
|- logout.php
|- register.php
|- style.min.css
My application resides inside localhost folder. Then, the db-config.php, loginClass.php and PasswordHashClass.php are saved inside the include folder. Now, in the main folder - the localhost folder, create four new files - index.php, register.php, logout.php and style.min.css. index.php will act as a place to login users. The register.php file will allow for registration of new users, while the logout.php will be used to logout and the style.min.css will be used to place our CSS. So, let's start with the index.php file.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Simple Login System</title>
<link rel="stylesheet" type="text/css" href="style.min.css" />
</head>
<body>
<?php
//include our loginClass file
include("includes/loginClass.php");
if( isset( $_POST['login'] ) ){
if( isset( $_POST['username'] ) && isset( $_POST['password'] ) ){
$login = new loginClass();
$username = $login->validateUser( $_POST['username'] );
if( !$username ) {
echo '<span class="error">Error, Invalid Username</span>';
}else{
$a = $login->login($_POST['username'], $_POST['password'] );
if( $a['message'] != 'error' ){
header("Location: /");
}else{
echo $a['msgbody'];
}
}
}
}
//Create a new object of the loginClass
$login = new loginClass();
//Check if not logged in
if( !$login->isUserLoggedIn() ){
//display login form
?>
<p>Enter username and Password to login</p>
<form action="" method="post" class="form-style-1">
<label for="username">Username</label>
<input type="text" name="username" id="username" placeholder="Username" />
<label for="password">Password</label>
<input type="password" name="password" id="password" placeholder="Password" />
<label></label>
<input type="submit" name="login" value="Login" />
<a href="register.php">Signup Here</a>
</form>
<?php
exit(0);
}
//Contents that is secured protected by login system.
echo 'Login successfully<br>';
echo '<a href="logout.php">Logout</a>';
?>
</body>
Here, the request variables is first checked. If they exist, it means the user has already post the data. Hence validate and login the user. The next part the user is then checked if already logged in or not. If not login then show login form. Now, let us see the register.php file. This file will look like this:
<?php
include("includes/loginClass.php");
$errors = FALSE;
$errorPwd = '';
$errorEmail = '';
$errorUser = '';
$msgDB = '';
if( isset( $_POST['register'] ) ){
if( isset( $_POST['username'] ) && isset( $_POST['password'] ) && isset( $_POST['useremail'] ) ){
$login = new loginClass();
//Validate and Sanitize data first
if( empty( $_POST['password'] ) ) { $errors = TRUE; $errorPwd = 'Password field is required'; }
$username = $login->validateUser( $_POST['username'] );
$useremail = $login->validateEmail( $_POST['useremail'] );
if( !$username ) { $errors = TRUE; $errorUser = 'Invalid Username'; }
if( !$useremail ) { $errors = TRUE; $errorEmail = 'Invalid Email'; }
//If no error then register
if( !$errors ){
$a = $login->registerUser( $username, $useremail, $_POST['password'] );
if( $a['message'] == 'error' ){
$msgDB = '<span class="error">' . $a['msgbody'] . '</span>';
}else{
$msgDB = $a['msgbody'] . '<br>You Can Login <a href="/">Click Here</a>';
}
}
}
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Simple Login System - Registration</title>
<link rel="stylesheet" type="text/css" href="style.min.css" />
<style type="text/css">
</style>
</head>
<body>
<p>Fill up the form to register</p>
<?php echo $msgDB; ?>
<form action="" method="post">
<label>Username: <span class="required">*</span></label>
<input type="text" name="username" placeholder="Username" /> <span class="error"><?php echo $errorUser; ?></span>
<label>Password: <span class="required">*</span></label>
<input type="password" name="password" placeholder="Password" /> <span class="error"><?php echo $errorPwd; ?></span>
<label>Email: <span class="required">*</span></label>
<input type="email" name="useremail" placeholder="Email" /> <span class="error"><?php echo $errorEmail; ?></span>
<label></label>
<input type="submit" name="register" value="Register" /> <a href="/">Login Here</a>
</form>
</body>
</html>
Here, also request variables is check, if user already post the data. Then validation takes place and finally insert the data. The logout.php file is rather simple and will just call the logout() method:
<?php
include("includes/loginClass.php");
$login = new loginClass();
$login->logout();
header( "Location: /" );
Finally a little touch of styling for the form to display. I've modified the CSS taken from Sanwebe.com for this tutorial. The modified CSS looks like this:
@charset "utf-8";
/* CSS Document */
body{
margin:0;
padding:10px;
font-family:Arial,Helvetica,sans-serif;font-size:1em;
background:#efefef
}
a{
text-decoration:none;color:#49D
}
label{
margin:3px;
padding:0;
display:block;
font-weight:700
}
input{
display:inline-block;
margin:3px
}
input[type=email],input[type=password],input[type=text]{
box-sizing:border-box;
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
border:1px solid #BEBEBE;
padding:7px;
outline:0
}
input[type=email]:focus,input[type=password]:focus,input[type=text]:focus{
-moz-box-shadow:0 0 8px #88D5E9;
-webkit-box-shadow:0 0 8px #88D5E9;
box-shadow:0 0 8px #88D5E9;
border:1px solid #88D5E9
}
input[type=button],input[type=submit]{
background:#49C;
padding:8px 15px;
border:none;color:#fff
}
input[type=button]:hover,input[type=submit]:hover{
background:#38B;
box-shadow:none;
-moz-box-shadow:none;
-webkit-box-shadow:none}
.error,.required{
color:red
}
In conclusion, we've successfully created a simple login script. Now what is next thing to do from here. Security is the main important part of any login system, this login system allows for data sanitization. Further extension to this class could be to include a reset-password function, send email on registration. I'm concluding this part here but in the near future, I will bring about some more changes to this loginClass and add those additional features. Till then stay tune.
Screenshot of the Login System