How to Build a Functional Login Form with PHP + Twitter Bootstrap

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!


View Demo | Download Source Files


About Michael Milstead

Michael is a front-end developer who has enjoyed building websites for the past seven years.

2 Comments

  • Landon says:

    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

  • Mark says:

    Hi Michael,

    How would I allow users to login with their email address rather than a username?

    Thanks

Leave a Reply

What is 10 + 9 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)
Start planning your project today. Get Started