https://plus.google.com/113677221098417166384/posts
The database will require a table to store the members, create a table called members:
In the classes folder their are two files: password.php and user.php.
password.php is used to provide the same hashing capability that exists
within php 5.5 it uses the same function names so versions 5.3 - 5.5
can use the same functions.
user.php is a class that contains methods to return the users hash (hashed password) as well as logging in, checking if a logged in session already exists and logging the user out.
I'll be going through the user.php methods as they are put to use.
Set the timezone and define the credentials for the database, next attempt to make a new PDO connection if the connection fails display the error and kill the page.
Next include the user class and make an instance of it, pass in the database object to the class to make use of the database.
Next I have a folder called layout in there is a header.php and footer.php these will contain any layout code that will be used on every page, this saves having to include the stylesheet each time. header.php is a typical header file, notice the title expects a $title variable, this will be created in the pages and made available to this file, also making use of Bootstrap this is optional and is not required.
Next footer.php this simply closed the body and html, that would be a
good place for placing tracking code or any javascript includes.
How these pages start is by including the config file then checking if the user should be redirected or not.
a call is made to the user object $user->is_logged_in() this will return true or false if the user is logged in.
The title and header.php file is also included on every page
For new registrations display a form consisting of username, email, password and confirm password
This is a standard form, one thing to note I make use of sticky forms
which means if their has been a validation error the fields that have
been filled out will be populated again with the supplied data, except
for passwords. Username and email would be restored.
This is done by doing an if statement, if the array $error is set meaning it exists then retrain the $_POST
If an error has been created it will be stored in an error array to display them loop through the array:
Once the new registration has been saved the form will post back to
the same page appending a $_GET key on the end of the URL the key will
be called action it will have a value of joined
(this technique is used through the project)
The form should only be processed if it has been submitted this can be checked by an if statement:
This way only if the form has been submitted does the validation start and database interactions commence.
This example checks the length of the username if it's less then 3 characters an error is created, if the first check passes the username is looked up to see if it already exists by passing the username to the database if a record is found an error is created.
These check the password to make sure the email has not been used, it's
important the email address is only used once, in the event the user
wants to reset their password a link will be emailed to that user.
After the validation if no errors have been created then carry on
The password provided cannot be stored as it is, that would be a huge security concern instead it's hashed by passing it to the user object inside a password_hash call this returns a hashed password which can then be stored in the database, this way no one can know what the password was apart from the user who entered it.
If your wondering how can the system login a user in without knowing the password; what happens when the user fills in the login form the password they enter is again hashed and then compared with the hash to see if its a match.
We also want to send an activation link to the user when they register to ensure their email address is active, for this we generate an activation code it will be sent in the emails and will form part of a url to validate the email address.
Next the user's details are saved to the database using a prepared
statement, the first page of the query tells MySQL what action to
perform in this case to add a new row and the table,columns to insert
into.
where their are columns starting with : like :username these are place holders that will be used to bind the username value to $stmt->execute call. This is done to avoid passing user provided data to the query directly and avoid chances of MySQL Injection.
calling lastInsertId followed by the primary key will return the id of the record just saved, this is needed for the next step.
Next send an email to the newly created user. Two constants defined in config.php will be used here
DIR - contains the full website address
SITEEMAIL - the email address used for emails
in the body of the email is a link activate.php?x=$id&y=$activasion this link is passing the id of the user $id and also the activation code when the user received this email, clicking the link will activate their account.
The last step is to redirect the page back to itself and adding an
action with the value of joined so the page know if to show a success
message.
once the data has been verified the users record is updated, the column active is changed from the token to hold 'Yes' to say they are active, this will only happen if the id and token passed match what's stored against that user.
The login page will be used to show messages if the users account has
been activated or password has been changed, the page will know which
message to show based on the value contained inside $_GET['action']
Next attempt to log the user in. Collect the username and password from
the form pass them to the users object in the login method this
internally will fetch the users hash by looking for the username in the
database once the hash is returned it's then passed to password_verify
if the hash and user's hash match it returns true which in turns sets a
session $_SESSION['loggedin'] to true otherwise false is returned.
Once the user is logged out redirect them.
In this example their is not a lot to the members page namely:
Next a token is created and saved to the users record, an email is sent to them containing a link to when clicked the token from the link is verified, if it passed the user is provided with a form to enter their new password, its then saved to the database.
This may seem like a long winded approach but it does prevent the password being sent by email which is not recommended.
To start with the form
If their is an $_GET['action'] show the correct message
Next process the form ensure the email matches a user:
Create the token
Next update the users record and set resetToken to the value of the
token and resetComplete to No that will be needed if the link is clicked
and password has been changed. Send an email to the user containing a
link that points to resetPassword.php?key=$token passing the token.
If $stop has been set then display that
If no errors have been created show a form to change the password
Once the form has been submitted validate the data then hash the
password update the users row and set resetComplete to Yes to indicate
the process is finished if the reset link is clicked again from email
the process will be halted.
The database will require a table to store the members, create a table called members:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| CREATE TABLE `members` ( `memberID` int (11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(60) NOT NULL, `email` varchar(255) NOT NULL, `active` varchar(255) NOT NULL, `resetToken` varchar(255) DEFAULT NULL, `resetComplete` varchar(3) DEFAULT 'No' , PRIMERY KEY (`memberID`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; |
user.php is a class that contains methods to return the users hash (hashed password) as well as logging in, checking if a logged in session already exists and logging the user out.
I'll be going through the user.php methods as they are put to use.
Config.php
Config.php will be included into all pages enable sessions and turn on output buffering this way headers can be used anywhere in the project.Set the timezone and define the credentials for the database, next attempt to make a new PDO connection if the connection fails display the error and kill the page.
Next include the user class and make an instance of it, pass in the database object to the class to make use of the database.
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
| <?php ob_start(); session_start(); //database credentials define( 'DBHOST' , 'localhost' ); define( 'DBUSER' , 'root' ); define( 'DBPASS' , '' ); define( 'DBNAME' , 'login' ); //application address define( 'SITEEMAIL' , 'noreply@domain.com' ); try { //create PDO connection $db = new PDO( "mysql:host=" .DBHOST. ";port=80;dbname=" .DBNAME, DBUSER, DBPASS); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $e) { //show error echo '<p class="bg-danger">' .$e->getMessage(). '</p>' ; exit; } //include the user class, pass in the database connection include( 'classes/user.php' ); $user = new User($db); ?> |
Next I have a folder called layout in there is a header.php and footer.php these will contain any layout code that will be used on every page, this saves having to include the stylesheet each time. header.php is a typical header file, notice the title expects a $title variable, this will be created in the pages and made available to this file, also making use of Bootstrap this is optional and is not required.
1
2
3
4
5
6
7
8
9
| <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "utf-8" > <title><?php if (isset($title)){ echo $title; }?></title> <link href= "//netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css" rel= "stylesheet" > <link rel= "stylesheet" href= "style/main.css" > </head> <body> |
1
2
3
| </body> </html> |
index.php
This is the root page the system loads by default, on this page their is a form for users to register to the site, along with links to the login page, if they are already a member. Also if the user is already logged in they will be redirect to the members page.How these pages start is by including the config file then checking if the user should be redirected or not.
a call is made to the user object $user->is_logged_in() this will return true or false if the user is logged in.
1
2
3
4
5
6
7
| <?php //include config require_once( 'includes/config.php' ); //check if already logged in move to home page //if logged in redirect to members page if ( $user->is_logged_in() ){ header( 'Location: memberpage.php' ); } |
1
2
3
4
5
| //define page title $title = 'Demo' ; //include header template require( 'layout/header.php' ); |
For new registrations display a form consisting of username, email, password and confirm password
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| <form role= "form" method= "post" action= "" autocomplete= "off" > <div class = "form-group" > <input type= "text" name= "username" id= "username" class = "form-control input-lg" placeholder= "User Name" value= "<?php if(isset($error)){ echo $_POST['username']; } ?>" tabindex= "1" > </div> <div class = "form-group" > <input type= "email" name= "email" id= "email" class = "form-control input-lg" placeholder= "Email Address" value= "<?php if(isset($error)){ echo $_POST['email']; } ?>" tabindex= "2" > </div> <div class = "row" > <div class = "col-xs-6 col-sm-6 col-md-6" > <div class = "form-group" > <input type= "password" name= "password" id= "password" class = "form-control input-lg" placeholder= "Password" tabindex= "3" > </div> </div> <div class = "col-xs-6 col-sm-6 col-md-6" > <div class = "form-group" > <input type= "password" name= "passwordConfirm" id= "passwordConfirm" class = "form-control input-lg" placeholder= "Confirm Password" tabindex= "4" > </div> </div> </div> <div class = "row" > <div class = "col-xs-6 col-md-6" ><input type= "submit" name= "submit" value= "Register" class = "btn btn-primary btn-block btn-lg" tabindex= "5" ></div> </div> </form> |
This is done by doing an if statement, if the array $error is set meaning it exists then retrain the $_POST
1
| value= "<?php if(isset($error)){ echo $_POST['email']; } ?>" |
1
2
3
4
5
6
| //check for any errors if (isset($error)){ foreach ($error as $error){ echo '<p class="bg-danger">' .$error. '</p>' ; } } |
(this technique is used through the project)
1
2
3
| if (isset($_GET[ 'action' ]) && $_GET[ 'action' ] == 'joined' ){ echo "<h2 class='bg-success'>Registration successful, please check your email to activate your account.</h2>" ; } |
1
2
| //if form has been submitted process it if (isset($_POST[ 'submit' ])){ |
Validation
The validation used is fairly basic and can be improved uponThis example checks the length of the username if it's less then 3 characters an error is created, if the first check passes the username is looked up to see if it already exists by passing the username to the database if a record is found an error is created.
1
2
3
4
5
6
7
8
9
10
11
| if (strlen($_POST[ 'username' ]) < 3){ $error[] = 'Username is too short.' ; } else { $stmt = $db->prepare( 'SELECT username FROM members WHERE username = :username' ); $stmt->execute(array( ':username' => $_POST[ 'username' ])); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!empty($row[ 'username' ])){ $error[] = 'Username provided is already in use.' ; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| if (strlen($_POST[ 'password' ]) < 3){ $error[] = 'Password is too short.' ; } if (strlen($_POST[ 'passwordConfirm' ]) < 3){ $error[] = 'Confirm password is too short.' ; } if ($_POST[ 'password' ] != $_POST[ 'passwordConfirm' ]){ $error[] = 'Passwords do not match.' ; } //email validation if (!filter_var($_POST[ 'email' ], FILTER_VALIDATE_EMAIL)){ $error[] = 'Please enter a valid email address' ; } else { $stmt = $db->prepare( 'SELECT email FROM members WHERE email = :email' ); $stmt->execute(array( ':email' => $_POST[ 'email' ])); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!empty($row[ 'email' ])){ $error[] = 'Email provided is already in use.' ; } } |
The password provided cannot be stored as it is, that would be a huge security concern instead it's hashed by passing it to the user object inside a password_hash call this returns a hashed password which can then be stored in the database, this way no one can know what the password was apart from the user who entered it.
If your wondering how can the system login a user in without knowing the password; what happens when the user fills in the login form the password they enter is again hashed and then compared with the hash to see if its a match.
We also want to send an activation link to the user when they register to ensure their email address is active, for this we generate an activation code it will be sent in the emails and will form part of a url to validate the email address.
1
2
3
4
5
6
7
8
| //if no errors have been created carry on if (!isset($error)){ //hash the password $hashedpassword = $user->password_hash($_POST[ 'password' ], PASSWORD_BCRYPT); //create the activation code $activasion = md5(uniqid(rand(), true )); |
where their are columns starting with : like :username these are place holders that will be used to bind the username value to $stmt->execute call. This is done to avoid passing user provided data to the query directly and avoid chances of MySQL Injection.
calling lastInsertId followed by the primary key will return the id of the record just saved, this is needed for the next step.
1
2
3
4
5
6
7
8
| $stmt = $db->prepare( 'INSERT INTO members (username,password,email,active) VALUES (:username, :password, :email, :active)' ); $stmt->execute(array( ':username' => $_POST[ 'username' ], ':password' => $hashedpassword, ':email' => $_POST[ 'email' ], ':active' => $activasion )); $id = $db->lastInsertId( 'memberID' ); |
DIR - contains the full website address
SITEEMAIL - the email address used for emails
in the body of the email is a link activate.php?x=$id&y=$activasion this link is passing the id of the user $id and also the activation code when the user received this email, clicking the link will activate their account.
1
2
3
4
5
6
| $to = $_POST[ 'email' ]; $subject = "Registration Confirmation" ; $body = "Thank you for registering at demo site.nn To activate your account, please click on this link:nn " .DIR. "activate.php?x=$id&y=$activasionnn Regards Site Admin nn" ; $additionalheaders = "From: <" .SITEEMAIL. ">rn" ; $additionalheaders .= "Reply-To: $" .SITEEMAIL. "" ; mail($to, $subject, $body, $additionalheaders); |
1
2
| header( 'Location: index.php?action=joined' ); exit; |
activate.php
This page checks for the id and activation code being passed from the url (this happens when the user clicks the link from their email)once the data has been verified the users record is updated, the column active is changed from the token to hold 'Yes' to say they are active, this will only happen if the id and token passed match what's stored against that user.
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
| <?php require( 'includes/config.php' ); //collect values from the url $memberID = trim($_GET[ 'x' ]); $active = trim($_GET[ 'y' ]); //if id is number and the active token is not empty carry on if (is_numeric($memberID) && !empty($active)){ //update users record set the active column to Yes where the memberID and active value match the ones provided in the array $stmt = $db->prepare( "UPDATE members SET active = 'Yes' WHERE memberID = :memberID AND active = :active" ); $stmt->execute(array( ':memberID' => $memberID, ':active' => $active )); //if the row was updated redirect the user if ($stmt->rowCount() == 1){ //redirect to login page header( 'Location: login.php?action=active' ); exit; } else { echo "Your account could not be activated." ; } } ?> |
Login.php
Now users can register they need a way to login, start off with a form that expects their username and password
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <form role= "form" method= "post" action= "" autocomplete= "off" > <div class = "form-group" > <input type= "text" name= "username" id= "username" class = "form-control input-lg" placeholder= "User Name" value= "<?php if(isset($error)){ echo $_POST['username']; } ?>" tabindex= "1" > </div> <div class = "form-group" > <input type= "password" name= "password" id= "password" class = "form-control input-lg" placeholder= "Password" tabindex= "3" > </div> <div class = "row" > <div class = "col-xs-9 col-sm-9 col-md-9" > <a href= 'reset.php' >Forgot your Password?</a> </div> </div> <hr> <div class = "row" > <div class = "col-xs-6 col-md-6" ><input type= "submit" name= "submit" value= "Login" class = "btn btn-primary btn-block btn-lg" tabindex= "5" ></div> </div> </form> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| if (isset($_GET[ 'action' ])){ //check the action switch ($_GET[ 'action' ]) { case 'active' : echo "<h2 class='bg-success'>Your account is now active you may now log in.</h2>" ; break ; case 'reset' : echo "<h2 class='bg-success'>Please check your inbox for a reset link.</h2>" ; break ; case 'resetAccount' : echo "<h2 class='bg-success'>Password changed, you may now login.</h2>" ; break ; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| public function login($username,$password){ $hashed = $ this ->get_user_hash($username); if ($ this ->password_verify($password,$hashed) == 1){ $_SESSION[ 'loggedin' ] = true ; return true ; } } <pre lang= "php" > //process login form if submitted if (isset($_POST[ 'submit' ])){ $username = $_POST[ 'username' ]; $password = $_POST[ 'password' ]; if ($user->login($username,$password)){ header( 'Location: memberpage.php' ); exit; } else { $error[] = 'Wrong username or password or your account has not been activated.' ; } } //end if submit |
Logout.php
To log a user out its very easy:
1
2
| //logout $user->logout(); |
memberpage.php
Once the user is logged in redirect them to the members only page (optional). To ensure a user can only access the page if logged in do a check:
1
2
| //if not logged in redirect to login page if (!$user->is_logged_in()){ header( 'Location: login.php' ); } |
In this example their is not a lot to the members page namely:
1
2
| <h2>Member only page</h2> <p><a href= 'logout.php' >Logout</a></p> |
reset.php
every system need the ability to reset a password in case it's forgotten, how this will work is a user enters their email address, a check is made to make sure its belongs to a user.Next a token is created and saved to the users record, an email is sent to them containing a link to when clicked the token from the link is verified, if it passed the user is provided with a form to enter their new password, its then saved to the database.
This may seem like a long winded approach but it does prevent the password being sent by email which is not recommended.
To start with the form
1
2
3
4
5
6
7
8
9
| <form role= "form" method= "post" action= "" autocomplete= "off" > <div class = "form-group" > <input type= "email" name= "email" id= "email" class = "form-control input-lg" placeholder= "Email" value= "" tabindex= "1" > </div> <hr> <div class = "row" > <div class = "col-xs-6 col-md-6" ><input type= "submit" name= "submit" value= "Sent Reset Link" class = "btn btn-primary btn-block btn-lg" tabindex= "2" ></div> </div> </form> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?php if (isset($_GET[ 'action' ])){ //check the action switch ($_GET[ 'action' ]) { case 'active' : echo "<h2 class='bg-success'>Your account is now active you may now log in.</h2>" ; break ; case 'reset' : echo "<h2 class='bg-success'>Please check your inbox for a reset link.</h2>" ; break ; } } ?> |
1
2
3
4
5
6
7
8
9
10
11
12
| //email validation if (!filter_var($_POST[ 'email' ], FILTER_VALIDATE_EMAIL)){ $error[] = 'Please enter a valid email address' ; } else { $stmt = $db->prepare( 'SELECT email FROM members WHERE email = :email' ); $stmt->execute(array( ':email' => $_POST[ 'email' ])); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (empty($row[ 'email' ])){ $error[] = 'Email provided is not on recognised.' ; } } |
1
2
| //create the activation code $token = md5(uniqid(rand(), true )); |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| $stmt = $db->prepare( "UPDATE members SET resetToken = :token, resetComplete='No' WHERE email = :email" ); $stmt->execute(array( ':email' => $row[ 'email' ], ':token' => $token )); //send email $to = $row[ 'email' ]; $subject = "Password Reset" ; $body = "Someone
requested that the password be reset. nnIf this was a mistake, just
ignore this email and nothing will happen.nnTo reset your password,
visit the following address: " .DIR. "resetPassword.php?key=$token" ; $additionalheaders = "From: <" .SITEEMAIL. ">rn" ; $additionalheaders .= "Reply-To: $" .SITEEMAIL. "" ; mail($to, $subject, $body, $additionalheaders); //redirect to index page header( 'Location: login.php?action=reset' ); exit; |
1
| |
resetPassword.php
First check the token been passed to the page matches a user
1
2
3
4
5
6
7
8
9
10
| $stmt = $db->prepare( 'SELECT resetToken, resetComplete FROM members WHERE resetToken = :token' ); $stmt->execute(array( ':token' => $_GET[ 'key' ])); $row = $stmt->fetch(PDO::FETCH_ASSOC); //if no token from db then kill the page if (empty($row[ 'resetToken' ])){ $stop = 'Invalid token provided, please use the link provided in the reset email.' ; } elseif($row[ 'resetComplete' ] == 'Yes' ) { $stop = 'Your password has already been changed!' ; } |
1
2
3
| if (isset($stop)){ echo "<p class='bg-danger'>$stop</p>" ; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <form role= "form" method= "post" action= "" autocomplete= "off" > <div class = "row" > <div class = "col-xs-6 col-sm-6 col-md-6" > <div class = "form-group" > <input type= "password" name= "password" id= "password" class = "form-control input-lg" placeholder= "Password" tabindex= "1" > </div> </div> <div class = "col-xs-6 col-sm-6 col-md-6" > <div class = "form-group" > <input type= "password" name= "passwordConfirm" id= "passwordConfirm" class = "form-control input-lg" placeholder= "Confirm Password" tabindex= "1" > </div> </div> </div> <hr> <div class = "row" > <div class = "col-xs-6 col-md-6" ><input type= "submit" name= "submit" value= "Change Password" class = "btn btn-primary btn-block btn-lg" tabindex= "3" ></div> </div> </form> |
No comments:
Post a Comment