Home arrow PHP arrow Simple and Secure PHP Login Script

Simple and Secure PHP Login Script

This programming tutorial will teach you how to create a simple, yet secure login script utilizing PHP using MySQL and bracing for XSS attack prevention.

By: Codex-M
Rating: starstarstarstarstar / 0
July 27, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

Recent advancements in PHP offer the developer a variety of tools to improve the security of login systems. The login script we will be developing consists of the following very important security features:

1.) A Hashed password (SHA-256+ Strong Salt) stored in a MySQL database.
2.) Anti-brute force protection using captcha challenges and denied access for more than “X” failed attempts relating to a specific IP address.
3.) User registration by providing username and password – protected with a captcha to prevent automated sign-ups.
4.) Protection of user-sessions against session hijacking and fixation.
5.) “X” minute idle timeout to automatically reset/destroy cookies (session lifetime control).
6.) No captcha for 1st 3 failed login; captcha will be provided for the remaining multiple login attempts (brute force prevention).
7.) Authentication for user logouts to prevent request forgeries. This can be extended to other features and not just logging out (for example: deleting files).
8.) No persistent logins - no “remember me” feature.
10.) User input validation against MySQL injections and most common XSS attacks.
11.) Preventing direct file access to authentication scripts.
12.) Easy to implement system with users providing all details in one file (config.php).
13.) For best security (recommended), you should implement the login script with HTTPS (if available). This will encrypt the sessions. The script will work in HTTPS.

What this project does not have:

1.) No feature for email support (such as sending an email after registration or user verification). This can, however, be easily added to the existing script.
2.) No “forget password” feature.
3.) No “remember me” feature.

This project is tested to work in PHP 5 and MySQL 5 with Apache and Linux/Unix servers. It consists of five very important scripts:

1.) register.php – used during new user registration.
2.) authenticate.php – provides user login authentication and control.
3.) config.php – user-specific configuration parameters and MySQL database connection parameters.
4.) 403forbidden.php – the landing page for brute force attackers and session hijackers with denied access.
5.) recaptchalib.php -recaptcha library

If you want to see how this project works before we get started, you can go to an actual working demo at: http://www.php-developer.org/securelogin/ to see the features at work.

For starters, let's discuss the main scripts: register.php and authenticate.php (see the comments below). Implementation procedures and the complete script can be found at the end of this article.

Register.php

<?php //require user configuration and database connection parameters require('config.php'); //pre-define validation parameters $usernamenotempty=TRUE;<br />
$usernamevalidate=TRUE;<br />
$usernamenotduplicate=TRUE;<br />
$passwordnotempty=TRUE;<br />
$passwordmatch=TRUE;<br />
$passwordvalidate=TRUE;<br />
$captchavalidationTRUE;//Check if user submitted the desired password and username if ((isset($_POST["desired_password"])) && (isset($_POST["desired_username"])) && (isset($_POST["desired_password1"])))  { //Username and Password has been submitted by the user<br />
//Receive and validate the submitted information<br />
//sanitize user inputs function sanitize($data){<br />
$data=trim($data);<br />
$data=htmlspecialchars($data);<br />
$data=mysql_real_escape_string($data);<br />
return 
$data;<br />
}<
br />
$desired_username=sanitize($_POST["desired_username"]);<br />
$desired_password=sanitize($_POST["desired_password"]);<br />
$desired_password1=sanitize($_POST["desired_password1"]); //validate username if (empty($desired_username)) {<br />
$usernamenotempty=FALSE;<br />
} else {<
br />
$usernamenotempty=TRUE;<br />
}<
br />
if ((!(
ctype_alnum($desired_username))) || ((strlen($desired_username)) >11)) {<br />
$usernamevalidate=FALSE;<br />
} else {<
br />
$usernamevalidate=TRUE;<br />
}<
br />
if (!(
$fetch mysql_fetch_arraymysql_query("SELECT `username` FROM `authentication` WHERE `username`='$desired_username'")))) { //no records for this user in the MySQL database $usernamenotduplicate=TRUE;<br />
}<br />
else {<
br />
$usernamenotduplicate=FALSE;<br />
//validate password if (empty($desired_password)) {<br />
$passwordnotempty=FALSE;<br />
} else {<
br />
$passwordnotempty=TRUE;<br />
} if ((!(
ctype_alnum($desired_password))) || ((strlen($desired_password)) < 8)) {<br />
$passwordvalidate=FALSE;<br />
} else {<
br />
$passwordvalidate=TRUE;<br />
}<
br />
if (
$desired_password==$desired_password1) {<br />
$passwordmatch=TRUE;<br />
} else {<
br />
$passwordmatch=FALSE;<br />
//Validate recaptcha require_once('recaptchalib.php');<br />
$resp recaptcha_check_answer ($privatekey$_SERVER["REMOTE_ADDR"], $_POST["recaptcha_challenge_field"],                                $_POST["recaptcha_response_field"]); if (!$resp->is_valid) { //captcha validation fails $captchavalidation=FALSE;<br />
} else {<br />
$captchavalidation=TRUE; <br />
} if ((
$usernamenotempty==TRUE)<br />
&& (
$usernamevalidate==TRUE)<br />
&& (
$usernamenotduplicate==TRUE)<br />
&& (
$passwordnotempty==TRUE)<br />
&& (
$passwordmatch==TRUE)<br />
&& (
$passwordvalidate==TRUE)<br />
&& (
$captchavalidation==TRUE)) { //The username, password and recaptcha validation succeeds.<br />
//Hash the password<br />
//This is very important for security reasons because <br />
//the attacker cannot still get the plain text password without brute <br />
//force. function HashPassword($input)<br />
//Credits: <a href="http://crackstation.net/hashing-security.html">http://crackstation.net/hashing-security.html</a><br />
//This is secure hashing the consist of strong hash algorithm sha 256 <br />
//and using highly random salt $salt = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); <br />
$hash hash("sha256"$salt $input); <br />
$final $salt $hash; <br />
return 
$final;<br />
}<
br />
$hashedpasswordHashPassword($desired_password); //Insert username and the hashed password to MySQL database mysql_query("INSERT INTO `authentication` (`username`, `password`) VALUES ('$desired_username', '$hashedpassword')") or die(mysql_error()); //Send notification to webmaster $message = "New member has just registered: $desired_username";<br />
mail($email$subject$message$from); //redirect to login page header(sprintf("Location: %s", $loginpage_url)); <br />
exit;<br />
}<
br />
}<
br />

<br />
<!DOCTYPE HTML><br />
<html><br />
<head><br />
<title>Register as a Valid User</title><br />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><br />
<style type="text/css"><br />
.invalid {<br />
border: 1px solid #000000;<br />
background: #FF00FF;<br />
}<br />
</style><br />
</head><br />
<body ><br />
<h2>User registration Form</h2><br />
<br /><br />
Hi! This private website is restricted to public access. If you want to see the content, please register below. You will be redirected to a login page after successful registration.<br />
<br /><br /> 

 

!-- Start of registration form --

<form action="<?php echo htmlentities($_SERVER['PHP_SELF']); 

method="POST"><br />
Username: (<i>alphanumeric less than 12 characters</i>) <input type="text" class="<?php if (($usernamenotempty==FALSE) || ($usernamevalidate==FALSE) || ($usernamenotduplicate==FALSE))  echo "invalid"; 

id="desired_username" name="desired_username"><br /><br /><br />
Password: (<i>alphanumeric greater than 8 characters</i>) <input name="desired_password" type="password" class="<?php if (($passwordnotempty==FALSE) || ($passwordmatch==FALSE) || ($passwordvalidate==FALSE)) echo "invalid"; 

id="desired_password" ><br /><br /><br />
Type the password again: <input name="desired_password1" type="password" class="<?php if (($passwordnotempty==FALSE) || ($passwordmatch==FALSE) || ($passwordvalidate==FALSE)) echo "invalid"; 

id="desired_password1" ><br /><br />
<
br /><br /><br />
Type the captcha below:<br />
<
br /> <br /><br />
<?
php<br />
require_once(
'recaptchalib.php');<br />
echo 
recaptcha_get_html($publickey);<br />

<br />
<br /><br /><br />
<input type="submit" value="Register"><br />
<br /><br /><br />
<a href="index.php">Back to Homepage</a><br />

!-- Display validation errors --

<?php if ($captchavalidation==FALSE) echo '<font color="red">Please enter correct captcha</font>'

<br /><br />
<?php if ($usernamenotempty==FALSE) echo '<font color="red">You have entered an empty username.</font>'

<br /><br />
<?php if ($usernamevalidate==FALSE) echo '<font color="red">Your username should be alphanumeric and less than 12 characters.</font>'

<br /><br />
<?php if ($usernamenotduplicate==FALSE) echo '<font color="red">Please choose another username, your username is already used.</font>'

<br /><br />
<?php if ($passwordnotempty==FALSE) echo '<font color="red">Your password is empty.</font>'

<br /><br />
<?php if ($passwordmatch==FALSE) echo '<font color="red">Your password does not match.</font>'

<br /><br />
<?php if ($passwordvalidate==FALSE) echo '<font color="red">Your password should be alphanumeric and greater 8 characters.</font>'

<br /><br />
<?php if ($captchavalidation==FALSE) echo '<font color="red">Your captcha is invalid.</font>'

<br /><br />
</form> <!-- End of registration form --> </body><br />
</html>

Authenticate.php

 <?php //Start PHP session session_start(); //require user configuration and database connection parameters require('config.php');<br />
if (($_SESSION['logged_in'])==TRUE) { //valid user has logged-in to the website<br />
//Check for unauthorized use of user sessions $iprecreate= $_SERVER['REMOTE_ADDR'];<br />
$useragentrecreate=$_SERVER["HTTP_USER_AGENT"];<br />
$signaturerecreate=$_SESSION['signature']; //Extract original salt from authorized signature $saltrecreate = substr($signaturerecreate, 0, $length_salt); //Extract original hash from authorized signature $originalhash = substr($signaturerecreate, $length_salt, 40); //Re-create the hash based on the user IP and user agent<br />
//then check if it is authorized or not $hashrecreate= sha1($saltrecreate.$iprecreate.$useragentrecreate);<br />
if (!($hashrecreate==$originalhash)) { //Signature submitted by the user does not matched with the<br />
//authorized signature<br />
//This is unauthorized access<br />
//Block it header(sprintf("Location: %s", $forbidden_url)); <br />
exit;    <br />
//Session Lifetime control for inactivity<br />
//Credits: <a href="http://bit.ly/Jx13y">http://bit.ly/Jx13y</a> if ((isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $sessiontimeout)))  {<br />
session_destroy();   <br />
session_unset();  //redirect the user back to login page for re-authentication $redirectback=$domain.'securelogin/';<br />
header(sprintf("Location: %s"$redirectback));<br />
}<
br />
$_SESSION['LAST_ACTIVITY'] = time(); <br />
//Pre-define validation $validationresults=TRUE;<br />
$registered=TRUE;<br />
$recaptchavalidation=TRUE//Trapped brute force attackers and give them more hard work $iptocheck= $_SERVER['REMOTE_ADDR'];<br />
$iptocheckmysql_real_escape_string($iptocheck);<br />
if (
$fetch mysql_fetch_arraymysql_query("SELECT `loggedip` FROM `ipcheck` WHERE `loggedip`='$iptocheck'"))) { //Already has some IP address records in the database<br />
//Get the total failed login attempts associated with this IP address $resultx = mysql_query("SELECT `failedattempts` FROM `ipcheck` WHERE `loggedip`='$iptocheck'");<br />
$rowx mysql_fetch_array($resultx);<br />
$loginattempts_total $rowx['failedattempts']; If ($loginattempts_total>$maxfailedattempt) { //too many failed attempts allowed, redirect and give 403 forbidden. header(sprintf("Location: %s", $forbidden_url)); <br />
exit;<br />
}<
br />
//Check if a user has logged-in if (!isset($_SESSION['logged_in'])) {<br />
    
$_SESSION['logged_in'] = FALSE;<br />
//Check if the form is submitted if ((isset($_POST["pass"])) && (isset($_POST["user"])) && ($_SESSION['LAST_ACTIVITY']==FALSE)) { //Username and password has been submitted by the user<br />
//Receive and sanitize the submitted information function sanitize($data){<br />
$data=trim($data);<br />
$data=htmlspecialchars($data);<br />
$data=mysql_real_escape_string($data);<br />
return 
$data;<br />
}<
br />
$user=sanitize($_POST["user"]);<br />
$passsanitize($_POST["pass"]); //validate username if (!($fetch = mysql_fetch_array( mysql_query("SELECT `username` FROM `authentication` WHERE `username`='$user'")))) { //no records of username in database<br />
//user is not yet registered $registered=FALSE;<br />
}<br />
if (
$registered==TRUE) { //Grab login attempts from MySQL database for a corresponding username $result1 = mysql_query("SELECT `loginattempt` FROM `authentication` WHERE `username`='$user'");<br />
$row mysql_fetch_array($result1);<br />
$loginattempts_username $row['loginattempt'];<br />
}<
br />
if((
$loginattempts_username>2) || ($registered==FALSE) || ($loginattempts_total>2)) { //Require those users with login attempts failed records to <br />
//submit captcha and validate recaptcha require_once('recaptchalib.php');<br />
$resp recaptcha_check_answer ($privatekey$_SERVER["REMOTE_ADDR"], $_POST["recaptcha_challenge_field"], $_POST["recaptcha_response_field"]); if (!$resp->is_valid) { //captcha validation fails $recaptchavalidation=FALSE;<br />
} else {<br />
$recaptchavalidation=TRUE; <br />
}<
br />
//Get correct hashed password based on given username stored in MySQL if ($registered==TRUE) { //username is registered in database, now get the hashed password $result = mysql_query("SELECT `password` FROM `authentication` WHERE `username`='$user'");<br />
$row mysql_fetch_array($result);<br />
$correctpassword $row['password'];<br />
$salt substr($correctpassword064);<br />
$correcthash substr($correctpassword6464);<br />
$userhash hash("sha256"$salt $pass);<br />
}<
br />
if ((!(
$userhash == $correcthash)) || ($registered==FALSE) || ($recaptchavalidation==FALSE)) { //user login validation fails $validationresults=FALSE; //log login failed attempts to database if ($registered==TRUE) {<br />
$loginattempts_username$loginattempts_username 1;<br />
$loginattempts_username=intval($loginattempts_username); //update login attempt records mysql_query("UPDATE `authentication` SET `loginattempt` = '$loginattempts_username' WHERE `username` = '$user'"); //Possible brute force attacker is targeting registered usernames<br />
//check if has some IP address records if (!($fetch = mysql_fetch_array( mysql_query("SELECT `loggedip` FROM `ipcheck` WHERE `loggedip`='$iptocheck'")))) { //no records<br />
//insert failed attempts $loginattempts_total=1;<br />
$loginattempts_total=intval($loginattempts_total);<br />
mysql_query("INSERT INTO `ipcheck` (`loggedip`, `failedattempts`) VALUES ('$iptocheck', '$loginattempts_total')"); <br />
} else { 
//has some records, increment attempts $loginattempts_total= $loginattempts_total + 1;<br />
mysql_query("UPDATE `ipcheck` SET `failedattempts` = '$loginattempts_total' WHERE `loggedip` = '$iptocheck'");<br />
}<
br />
//Possible brute force attacker is targeting randomly if ($registered==FALSE) {<br />
if (!($fetch mysql_fetch_arraymysql_query("SELECT `loggedip` FROM `ipcheck` WHERE `loggedip`='$iptocheck'")))) { //no records<br />
//insert failed attempts $loginattempts_total=1;<br />
$loginattempts_total=intval($loginattempts_total);<br />
mysql_query("INSERT INTO `ipcheck` (`loggedip`, `failedattempts`) VALUES ('$iptocheck', '$loginattempts_total')"); <br />
} else { 
//has some records, increment attempts $loginattempts_total= $loginattempts_total + 1;<br />
mysql_query("UPDATE `ipcheck` SET `failedattempts` = '$loginattempts_total' WHERE `loggedip` = '$iptocheck'");<br />
}<
br />
}<
br />
} else { 
//user successfully authenticates with the<br />
//provided username and password<br />
//Reset login attempts for a specific username to 0 <br />
//as well relating to the IP address $loginattempts_username=0;<br />
$loginattempts_total=0;<br />
$loginattempts_username=intval($loginattempts_username);<br />
$loginattempts_total=intval($loginattempts_total);<br />
mysql_query("UPDATE `authentication` SET `loginattempt` = '$loginattempts_username' WHERE `username` = '$user'");<br />
mysql_query("UPDATE `ipcheck` SET `failedattempts` = '$loginattempts_total' WHERE `loggedip` = '$iptocheck'"); //Generate unique signature of the user based on IP address<br />
//and the browser then append it to session<br />
//This will be used to authenticate the user session <br />
//To make sure it belongs to an authorized user and not to anyone else.<br />
//generate random salt function genRandomString() { //credits: <a href="http://bit.ly/a9rDYd">http://bit.ly/a9rDYd</a> $length = 50;<br />
$characters "0123456789abcdef";      <br />
for (
$p 0$p $length $p++) {<br />
$string .= $characters[mt_rand(0strlen($characters))];<br />
}<
br />
return 
$string;<br />
$random=genRandomString();<br />
$salt_ipsubstr($random0$length_salt); //hash the ip address, user-agent and the salt $useragent=$_SERVER["HTTP_USER_AGENT"];<br />
$hash_usersha1($salt_ip.$iptocheck.$useragent); //concatenate the salt and the hash to form a signature $signature= $salt_ip.$hash_user; //Regenerate session id prior to setting any session variable<br />
//to mitigate session fixation attacks session_regenerate_id(); //Finally store user unique signature in the session<br />
//and set logged_in to TRUE as well as start activity time $_SESSION['signature'] = $signature;<br />
$_SESSION['logged_in'] = TRUE;<br />
$_SESSION['LAST_ACTIVITY'] = time(); <br />
}<
br />
} <
br />
if (!
$_SESSION['logged_in']): <br />

<br />
<!DOCTYPE HTML><br />
<html><br />
<head><br />
<title>Login</title><br />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><br />
<style type="text/css"><br />
.invalid {<br />
border: 1px solid #000000;<br />
background: #FF00FF;<br />
}<br />
</style><br />
</head><br />
<body ><br />
<h2>Restricted Access</h2><br />
<br /><br />
Hi! This private website is restricted to public access. Please enter username and password to proceed.<br />
<br /><br />

<!-- START OF LOGIN FORM -->

 <form action="<?php echo htmlentities($_SERVER['PHP_SELF']); 

method="POST"><br />
Username:  <input type="text" class="<?php if ($validationresults==FALSE) echo "invalid"; 

id="user" name="user"><br />
Password: <input name="pass" type="password" class="<?php if ($validationresults==FALSE) echo "invalid"; 

id="pass" ><br />
<
br /><br /><br />
<?
php if (($loginattempts_username 2) || ($registered==FALSE) || ($loginattempts_total>2)) { 

<br />
Type the captcha below:<br />
<br /> <br /><br />
<?php<br />
require_once(
'recaptchalib.php');<br />
echo 
recaptcha_get_html($publickey);<br />

<br />
<br /><br />
<?php 

<br />
<?php if ($validationresults==FALSE) echo '<font color="red">Please enter valid username, password or captcha (if required).</font>'

<br /><br />
<input type="submit" value="Login">                   <br />
</form> <!-- END OF LOGIN FORM --> <br /><br />
<br /> If you are not registered. You can register by clicking <a href="register.php">here</a>.<br />
</body><br />
</html><br />
<?php<br />
exit();<
br />
endif;<
br />


Implementation

1.) Create two MySQL database tables:

Table Name 1: authentication
Fields and table structure:

Table Name 2: ipcheck
Fields and table structure:

2.) Download the project here: http://www.php-developer.org/php-secure-authentication-of-user-logins/

3.) Unzip and fill in the required information in config.php (between “START OF USER CONFIGURATION” and “END OF USER CONFIGURATION”).

4.) Upload the securelogin folder to the root directory of your website. Access  http://www.example.com/securelogin/ using a browser, then try registering a user. Test all of the features and customize them to your liking. You can customize index.php, about.php and all files that are not related to the core scripts. Do not forget to add:

require('authenticate.php');

to the top of all scripts you want to protect.

For improved security, set the length of salt to near maximum in config.php and use HTTPS for your login.

Note: This article is a 2011 update of our previous series, Creating a Secure PHP Login Script.


 
 
>>> More PHP Articles          >>> More By Codex-M
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: