For this tutorial, we’ll look at building a completely functional login + registration system with a design based off of Twitter Bootstrap. The login form will pop out as a dropdown from the navbar and maintain responsiveness when scaled down. Setting up with SQL + PHP is easy. Let’s take a look at how it all works!
View Demo | Download Source Files
Configuration
For security purposes, you’ll want to have your user data stored with MySQL. If you can access phpMyAdmin on your server, create a database with any name, then construct a table to hold users’ info by executing this bit of SQL:
CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `password` char(64) COLLATE utf8_unicode_ci NOT NULL, `salt` char(16) COLLATE utf8_unicode_ci NOT NULL, `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), UNIQUE KEY `email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;
When the table is successfully set up, create the first file, config.php, with this PHP code:
<?php // These variables define the connection information for your MySQL database $username = "databaseuser"; $password = "databasepass"; $host = "localhost"; $dbname = "yourdatabase"; $options = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'); try { $db = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options); } catch(PDOException $ex){ die("Failed to connect to the database: " . $ex->getMessage());} $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); header('Content-Type: text/html; charset=utf-8'); session_start(); ?>
This file establishes a connection to the database we’ve just created and sets a few options. To connect, enter your own details in the first few lines to replace databaseuser, databasepass, and yourdatabase. Later, we’ll call this information into other files to keep that connection open.
User Registration
From here, we can design the registration and login processes with Bootstrap and some functional PHP. Let’s start it off with register.php. Insert this script at the top of any markup:
<?php require("config.php"); if(!empty($_POST)) { // Ensure that the user fills out fields if(empty($_POST['username'])) { die("Please enter a username."); } if(empty($_POST['password'])) { die("Please enter a password."); } if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { die("Invalid E-Mail Address"); } // Check if the username is already taken $query = " SELECT 1 FROM users WHERE username = :username "; $query_params = array( ':username' => $_POST['username'] ); try { $stmt = $db->prepare($query); $result = $stmt->execute($query_params); } catch(PDOException $ex){ die("Failed to run query: " . $ex->getMessage()); } $row = $stmt->fetch(); if($row){ die("This username is already in use"); } $query = " SELECT 1 FROM users WHERE email = :email "; $query_params = array( ':email' => $_POST['email'] ); try { $stmt = $db->prepare($query); $result = $stmt->execute($query_params); } catch(PDOException $ex){ die("Failed to run query: " . $ex->getMessage());} $row = $stmt->fetch(); if($row){ die("This email address is already registered"); } // Add row to database $query = " INSERT INTO users ( username, password, salt, email ) VALUES ( :username, :password, :salt, :email ) "; // Security measures $salt = dechex(mt_rand(0, 2147483647)) . dechex(mt_rand(0, 2147483647)); $password = hash('sha256', $_POST['password'] . $salt); for($round = 0; $round < 65536; $round++){ $password = hash('sha256', $password . $salt); } $query_params = array( ':username' => $_POST['username'], ':password' => $password, ':salt' => $salt, ':email' => $_POST['email'] ); try { $stmt = $db->prepare($query); $result = $stmt->execute($query_params); } catch(PDOException $ex){ die("Failed to run query: " . $ex->getMessage()); } header("Location: index.php"); die("Redirecting to index.php"); } ?>
First, this script calls the database connection, then it ensures all form fields are filled, checks if the username is unique, and adds security measures to the password. Afterwards, it inserts the new user into a table row within the database and redirects the browser back to the homepage, where the new user can now login. The registration form markup looks like:
<form action="register.php" method="post"> <label>Username:</label> <input type="text" name="username" value="" /> <label>Email:</label> <input type="text" name="email" value="" /> <label>Password:</label> <input type="password" name="password" value="" /> <br /><br /> <input type="submit" class="btn btn-info" value="Register" /> </form>
Logging In
To build our main file, index.php, we’ll start it with the actual login script:
<?php require("config.php"); $submitted_username = ''; if(!empty($_POST)){ $query = " SELECT id, username, password, salt, email FROM users WHERE username = :username "; $query_params = array( ':username' => $_POST['username'] ); try{ $stmt = $db->prepare($query); $result = $stmt->execute($query_params); } catch(PDOException $ex){ die("Failed to run query: " . $ex->getMessage()); } $login_ok = false; $row = $stmt->fetch(); if($row){ $check_password = hash('sha256', $_POST['password'] . $row['salt']); for($round = 0; $round < 65536; $round++){ $check_password = hash('sha256', $check_password . $row['salt']); } if($check_password === $row['password']){ $login_ok = true; } } if($login_ok){ unset($row['salt']); unset($row['password']); $_SESSION['user'] = $row; header("Location: secret.php"); die("Redirecting to: secret.php"); } else{ print("Login Failed."); $submitted_username = htmlentities($_POST['username'], ENT_QUOTES, 'UTF-8'); } } ?>
This does just what you’d expect: opens the database, checks the table for whatever the user has input, opens the secret page if successful, and fails if not.
Now we can structure the rest of our index page with the following Bootstrap markup. This can also be used for the registration and secret page; just switch out the hero-unit content and adjust the navbar as necessary.
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script> <script src="assets/bootstrap.min.js"></script> <link href="assets/bootstrap.min.css" rel="stylesheet" media="screen"> <style type="text/css"> body { background: url(assets/bglight.png); } .hero-unit { background-color: #fff; } .center { display: block; margin: 0 auto; } </style> </head> <body> <div class="navbar navbar-fixed-top navbar-inverse"> <div class="navbar-inner"> <div class="container"> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <a class="brand">PHP Signup + Bootstrap Example</a> <div class="nav-collapse collapse"> <ul class="nav pull-right"> <li><a href="register.php">Register</a></li> <li class="divider-vertical"></li> <li class="dropdown"> <a class="dropdown-toggle" href="#" data-toggle="dropdown">Log In <strong class="caret"></strong></a> <div class="dropdown-menu" style="padding: 15px; padding-bottom: 0px;"> <form action="login.php" method="post"> Username:<br /> <input type="text" name="username" value="<?php echo $submitted_username; ?>" /> <br /><br /> Password:<br /> <input type="password" name="password" value="" /> <br /><br /> <input type="submit" class="btn btn-info" value="Login" /> </form> </div> </li> </ul> </div> </div> </div> </div> <div class="container hero-unit"> <h1>There's secret content to be had within!</h1> <p>But you can't access it just yet! You'll need to log in first. Use Bootstrap's nifty navbar dropdown to access the form.</p> <h2>There are 2 ways you can log in:</h2> <ul> <li>Try out your own user + password with the <strong>Register</strong> button in the navbar.</li> <li>Use the default credentials to save time:<br /> <strong>user:</strong> admin<br /> <strong>pass:</strong> password<br /></li> </ul> </div> </body> </html>
As a bonus, because the form post is self-referential, we can have this dropdown exist on any other page’s nav and still have it process through the script here! We can now build out the secret content’s file.
The Secret Page
We’re going to begin secret.php with the config call and this redirect, which will ensure an unregistered user can’t access the page contents:
<?php require("config.php"); if(empty($_SESSION['user'])) { header("Location: index.php"); die("Redirecting to index.php"); } ?>
Keep that bit at the top and proceed with the rest of the markup for whatever you want this page to be. In the demo, I’ve modeled the secret page after the index, but with different content in the hero-unit. (Check out the demo to see the hidden content!)
Finishing Up
Now we’re ready to end the session by logging out. We’ll want to replace the login link in our secret page’s nav bar with this:
<li><a href="logout.php">Log Out</a></li>
All that’s needed in your logout.php file is the following:
<?php require("config.php"); unset($_SESSION['user']); header("Location: index.php"); die("Redirecting to: index.php"); ?>
As you can see, the session is ended in one line, then we’re directed back to the index page. Try out the demo and register your own user/pass combo! Check out the source files below if you would like to test this on your own server. Let us know what you think!
I love your tutorial here…this is exactly what i was looking for! I have everything setup per your instructions, but when i register a user i get the following error.
Failed to run query: SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘salt’ in ‘field list’
Do you have a fix for this?
Thanks,
L
Hi Michael,
How would I allow users to login with their email address rather than a username?
Thanks