The PASSWORD

The PASSWORD. The PASSWORD project is an advanced full-stack project that builds on the previous project, The ACCOUNT. We will build features that help to manage accounts and protect security. A registration process, password encryption, AJAX/JSON and jQuery page updates are the main features.

You can download scripts for completed versions of the ACCOUNT project here if you need them.

You can open a completed version of this project in your browser here.

If you want to download completed versions of the PHP scripts and image files for this project, you can find them here.

This project has several steps:

The submission process for the forms we create for this project will use AJAX instead of the usual form submission process. AJAX is more complicated to set up, but provides many advantages. Interaction with the server can be minimized, with fewer extra pages to build and maintain. PHP scripts that handle database queries can be limited to just that function, and don't require extra work to present the data to the user. User input can be handled immediately, without refreshing a page. In this case, AJAX will allow a more robust error message system for user feeedback, and provide more experience with jQuery methods for redrawing the page.

Project Set-up. As before, set up a directory in the htdocs folder to hold your project. I recommend naming it n413_pw. We will use the ACCOUNT project as the basis for building this one, so copy all the files from the ACCOUNT project into your new project folder. You can find them here if you need them.

Be sure your MAMP/XAMPP server is running and check your connection script (n413_connect.php) to be sure it uses the correct database connection credentials if you copied in a new version. Check to be sure you have copies of the Bootstrap and jQuery Javascript files, and the Bootstrap CSS file.

Registration Page. Create a special page in your project for new users to register for accounts. Name the page register.php. The page will use elements from the Log-In Form and theContact Form, so have those files open for cut-and-paste time-savers.

Nav-Bar. Begin by adding a link for "Register" in the nav-bar. Open head.php and find the <ul> tag that holds the Nav-bar links. Look for the second <ul> tag that floats to the right side of the nav-bar. You will see a PHP script with logic for what to display, depending on the log-in status. Only users who don't already have a log-in would need to register, so we need to add the <li> tags for the "Register" link in the same "if/else" clause as the "Log-In" link:

(head.php)
...
    <ul id="right_navbar" class="navbar-nav ml-auto mr-5">
        <?php 
        session_start();
        if(isset($_SESSION["user_id"])){
            if($_SESSION["role"] > 0){
                echo '
                <li id="messages_item" class="nav-item">
                    <a id="messages_link" class="nav-link" href="messages.php">Messages</a>
                </li>';
            }
            echo '
            <li id="logout_item" class="nav-item">
                <a id="logout_link" class="nav-link" href="logout.php">Log-Out</a>
            </li>';
        }else{
            echo ' 
            <li id="register_item" class="nav-item">
                <a id="register_link" class="nav-link" href="register.php">Register</a>
            </li>      
            <li id="login_item" class="nav-item">
                <a id="login_link" class="nav-link" href="login.php">Log-In</a>
            </li>';
        }
        ?>
    </ul>            
...

Check your project in a browser to see the "Register" link in the nav-bar.

The form we will create is a combination of the Log-in Form and the Contact Form, with some modifications. For simplicity, open login.php, and copy the whole script into register.php. Then open the contact form, form.php, and copy the "E-mail" label and input. Paste that into register.php between the username and password inputs. Make the following modifications:

  • Just after the closing PHP tag, add a <style> tag, and add a style for ".error_msg", and set CSS styles for "display:none;" and "color:#900;". Be sure to close the tag.
  • Change the word "Log-in" in the headline to "Registration"
  • Give the <form> tag an id of "register_form".
  • Change the "action" attribute to empty quotes (""). Later the "action" will be replaced by a jQuery function.
  • Delete the <br/> tags at the end of the input lines, and wrap the lines (including the labels) in <div> tags. Also wrap the "Submit" button line in <div> tags.
  • For the "E-mail" and "Password" lines add a Bootstrap spacer class of "mt-3" (margin-top) to the <div> tag. Add a class of "mt-5" to the "Submit" button <div> tag.
  • In the <script> tag, change var this_page = "login"; to var this_page = "register";
  • Change the next line from var page_title = 'AMP JAM Site | Login'; to var page_title = 'AMP JAM Site | Register';
  • Add empty <div> tags with the class "error_msg" underneath the input lines. Add two empty <div> tag lines under the "Username" and "E-mail" inputs, and one under the "Password" input.
  • Add the following id attributes to the empty "error_msg" <div> tags:
    • "username_length"
    • "username_exists"
    • "email_exists"
    • "email_validate"
    • "password_length"
It should look like this:
(register.php)
<?php
    include("head.php");
?>
<style>
    .error_msg { display:none;color:#C00; }
</style>

<div class="container-fluid">
    <div id="headline" class="row  mt-5">
        <div class="col-12 text-center">
            <h2>Full Stack Amp Jam Registration</h2>
        </div> <!-- /col-12 -->
    </div> <!-- /row -->
    <form id="register_form" method="POST" action="">
    <div class="row mt-5">
        <div class="col-4"></div>  <!-- spacer -->
        <div id="form-container" class="col-4">
            <div>User Name: <input type="text" id="username" name="username" class="form-control" value="" placeholder="Enter User Name" required/></div>
            <div id="username_length" class="error_msg"></div>
            <div id="username_exists" class="error_msg"></div>
            <div class="mt-3">E-mail: <input type="email" id="email" name="email" class="form-control" value="" placeholder="Enter E-mail" required/></div>
            <div id="email_exists" class="error_msg"></div>
            <div id="email_validate" class="error_msg"></div>
            <div class="mt-3">Password: <input type="password" id="password" name="password" class="form-control" value="" placeholder="Enter Password" required/></div>
            <div id="password_length" class="error_msg"></div>
            <div class="mt-5"><button type="submit" id="submit" class="btn btn-primary float-right">Submit</button></div>
        </div>  <!-- /#form-container -->
    </div>  <!-- /.row -->
</form>
</body>
<script>
    var this_page = "register";
    var page_title = 'AMP JAM Site | Registration';
		
    $(document).ready(function(){ 
            document.title = page_title;
            navbar_update(this_page);
        }); //document.ready
</script>
</html>

The "action" attribute was deleted because jQuery will be used to submit the form, using a "submit" handler and a jQuery $.post() function.

There are 5 error messages that may be sent from the server using an AJAX call to alert the user to problems with the form. These will be displayed in the <div> tags under the inputs, but they are set to "display:none" until they are needed.

Check it in your browser, and it should look like this.

We will come back to this later and set it up to work with AJAX, but for now, it will work well enough to put some other pieces in place, starting with the table for storing the account information.

New Improved Users Table The way we will use the users table for this version of the project is not compatible with the earlier projects. To avoid breaking the projects built so far, create a new users table, which we will call users_hash, since we will encrypt, or "hash" the passwords. We can simply copy the existing users table and rename it. phpMyAdmin can do this easily. Go to the users table and click on the "Operations" tab.

Look down the page to the "Copy Table to (database.table)" section. You will see a selection box for the database, and a form field for the table name. Type "users_hash" into the field. Then notice the radio buttons below. You have a few options for how the database can be copied. "Structure Only" is the best choice here. That option will produce an empty table with the right structure. Don't worry about the other settings. Click "Go". The table should appear in the left-side column. Otherwise, refresh the browser to see it. The new table is ready for use.

Password Encryption Password encryption involves processing a string of numbers or characters with a mathematical function that changes the original string into a very large number. This number is usually represented by hexadecimal (base 16) notation, and can run into the hundreds of places. The number is called a "hash". The idea is that the original input data will always produce the same hash, and that no other input data will produce a duplicate hash.

For a log-in application, the user's original password is hashed before it is stored in a database. Once this happens, no one but the user knows what the password is. To authenticate the user, the supplied log-in password is hashed the same way as the original password, and the two are compared. If they match, the password is authentic.

There have been many different encryption algorithms over the years. CRC-8 was widely used at one time. It produced a hash that was only 8 places long. While still used as a method for verifying data integrity, the ever-increasing power of computers has made it simple to "break", and CRC-8 is no longer used for security purposes. The same problem occurred with MD5, a 128-bit hash algorithm developed in the 1990's. Computing power made it possible to develop "rainbow tables" - large dictionaries of strings and their MD5 hash results. This led to the use of SHA-1 encryption, then SHA-128, SHA-256, etc., etc., etc.

A cat-and-mouse game between algorithm developers and encryption-breaking hackers has produced a series of ever more complex encryption methods which are eventually broken by ever more powerful computing resources. The resulting strategy has become one of assuming that the encryption will eventually be broken, but that the time required to break it will provide a temporary period of security.

The idea that complexity buys time has resulted in the "salt" strategy. A string is added to the user's password to make it longer and more complex to break. This has the added benefit of making it unclear which part of a broken password is the original password, and which part is the "salt".

The need to periodically change encryption algorithms and the use of "salt" as a way to add complexity and obscurity to the encrypted passwords has led to the password_hash() method of encrypting passwords with PHP. It is very simple to run a standard hash algorithm on your passwords. All it takes is "$password = MD5($password)". However, it is impossible to convert the passwords in your database from MD5 to SHA-1. That means you are always stuck with the encryption algorithm you used back when it seemed like a good way to go. The password_hash() method will use the current encryption method, encode the algorithm used into the hash, and add "salt", too. When you need to authenticate the password, the companion method, password_verify(), will know exactly how to verify the log-in password, regardless of the method used to encrypt it.

This will not update all the old passwords in your database, but it will mean that new passwords will get the latest encryption methods. And users who change their passwords will get the update, too. Plus, you don't have to worry about whether you are staying current. The regular PHP updates will do that for you.

Let's build one of these systems...

n413register.php Create a new PHP file, n413register.php in your project. This script will take the registration data from register.php, check for errors, encrypt the password, and insert the new account record in the database. If the database INSERT is successful, it will log-in the user and send a JSON encoded message back to the browser with a success message, or feedback about errors if things did not work.

These are the errors we need to handle:

  • Minimum String Lengths. We need to check for minimum string lengths with usernames and passwords to avoid simple passwords or empty usernames. Use 5 characters for usernames, and 8 characters for passwords.
  • Email Validation. While we have email validation working on the form (courtesy of Bootstrap), it is always good practice to validate on the server, also.
  • Unique Accounts. It should not be possible to register the same account twice. We need to check whether a user exists before we insert the new account record into the database. This means checking both the username and email, since we will use email to reset the password later, and it must be unique to the account.

The script is very similar to n413auth.php, so you should have it open for copying. To begin working with n413register.php, write the opening PHP tag, and add the "include" for n413connect.php. Also add the sanitize() function.

Next we need to create an array to hold the various messages that may be sent to the browser. Call the array "$messages", and add keys for "status", "errors", "success", "failed", and the 5 "error_msg" ids from register.php. The array will look like this:

(n413register.php)
...
    $messages = array();
    $messages["status"] = 0;
    $messages["errors"] = 0;
    $messages["username_length"] = "";
    $messages["username_exists"] = "";
    $messages["email_exists"] = "";
    $messages["email_validate"] = "";
    $messages["password_length"] = "";
    $messages["success"] = "";
    $messages["failed"] = "";
...

Check Minimum String Lengths. Add the lines that initialize the $username, $email and $password variables. With n413register.php, instead of directly "sanitizing" the $_POST values, check them for errors first. Start with the "username". Trim it, then check the length with the PHP strlen() function. If the length is good, run the username through the sanitize() function. If the username fails the length test, set the $messages["error"] value to "1" (Boolean true) and add a user feedback message to $messages["username_length"].

Run the same check on the password, using 8 characters as the minimum. If the password passes the check, run password_hash() to encrypt the password:

(n413register.php)
...
    $username = "";
    $email = "";
    $password = "";
        
    if(isset($_POST["username"])) { $username = $_POST["username"]; }
    $username = trim($username);
    if( strlen($username) < 5 ){
    	$messages["error"] = 1;
        $messages["username_length"] = "The Username must be at least 5 characters long.";
    }else{
        $username = sanitize($username);
    }
    
    if(isset($_POST["password"])) { $password = $_POST["password"]; }
    $password = trim($password);
    if( strlen($password) < 8 ){
    	$messages["error"] = 1;
        $messages["password_length"] = "The Password must be at least 8 characters long.";
    }else{
        $encrypted_password = password_hash($password, PASSWORD_DEFAULT);
        if($encrypted_password){ 
            $password = $encrypted_password; 
        }else{
            $messages["errors"] = 1;
            $messages["password_length"] = "Password encryption failed.  You cannot register at this time";
        }
    }
...

Notice the constant "PASSWORD_DEFAULT" used as the second argument for password_hash(). This allows the behavior described in the "Password Encryption" section (above) to work. PHP will specify the algorithm used to encrypt the password. However, a specific algorithm can be used, if neeeded.

It is possible for the password encryption to fail (encryption can be time intensive, and the server can time out). In that case, password_hash() will return false, so you should test for a result, and use an error to handle the problem.

One other issue to be aware of: If your PHP version is older than version 5.5.0, password_hash() will trigger a server error. For older PHP versions, you can use a script called password.php to add the password_hash() functionality. You will find it in the script download package for this project if you need it. (lib/password.php)

Validate the Email Address. Add an error check to validate the email address. The browser form checks email addresses as part of Bootstrap's functionality, but it is good practice to check on the server, too. PHP provides a "filter_var" function to validate email. If the function returns true, the address is good and you should sanitize() it for the database. Otherwise, an error is triggered.

(n413register.php)
...
    if(isset($_POST["email"])) { $email = $_POST["email"]; }   
    if (filter_var($email, FILTER_VALIDATE_EMAIL)){
    	$email = sanitize($email);
    }else{
    	$messages["errors"] = 1;
    	$messages["email_validate"] = "There are problems with the e-mail address.  Please correct them.";
    }
...

Unique Accounts . This step ensures we have unique values for the account. Don't run these checks unless there have been no errors so far. We want to make sure the values are sanitized for the database, and if there are problems upstream, the values will not be sanitized, and are useless anyway. Wrap the code for these checks in an "if" statement that checks the value of $messages["errors"]. If there are "not" ("!") errors, the tests proceed.

Use an SQL "SELECT" query to check whether a username or email already exists. If any rows come back from the query, the test fails, and should cause an error. Check the number of rows returned in the result with mysqli_num_rows($result). The username and email should be checked independently:

(n413register.php)
...
    if( ! $messages["errors"]){
        $sql = "SELECT * FROM `users_hash` WHERE username = '".$username."'";
        $result = mysqli_query($link, $sql);
        if( mysqli_num_rows($result) > 0){
            $messages["errors"] = 1;
            $messages["username_exists"] = "This Username is already in use.  Please provide a different Username";
        }
        
        $sql = "SELECT * FROM `users_hash` WHERE email = '".$email."'";
        $result = mysqli_query($link, $sql);
        if( mysqli_num_rows($result) > 0){
            $messages["errors"] = 1;
            $messages["email_exists"] = "This E-mail address is already in use.  You cannot register another account for this E-mail.";
        }
    } //if( ! $messages["errors"])

INSERT Query and Log-In. Write the "INSERT" query and the log-in code for the next step. Check whether there are any errors first.

Note: Remember the phpMyAdmin shortcut for creating "INSERT" queries. (From The FORM exercise.)

Because the database table is using AUTO_INCREMENT to set the id, the id value is "NULL" in the "INSERT" query. But we will need to know the id to create a log-in. The mysqli library has a method for retrieving the last id added to a database when AUTO_INCREMENT is used. It is mysqli_insert_id(). Notice how it is used here:

(n413register.php)
...
    if( ! $messages["errors"]){
        $sql = "INSERT INTO `users_hash` (`id`, `username`, `email`, `password`, `role`) 
                VALUES (NULL, '".$username."', '".$email."', '".$password."', '0')";
        $result = mysqli_query($link, $sql); 
        
        $user_id = mysqli_insert_id($link);
        if($user_id){
            session_start();
            $_SESSION["user_id"] = $user_id;
            $_SESSION["role"] = "0";
        } // if($user_id)
    }  //if( ! $messages["errors"])    
...

mysqli_insert_id() requires the database connection ($link) as an argument. The returned number will be the user_id. The role will be "0" for a new account. Once we have those two values, we have a log-in, and it is stored in the $_SESSION variable.

User Messages. The two remaining user messages provide feedback for a successful login -- or for a failed log-in if a mystery happens. In addition, we want to provide a "status" message for the browser to signal the login status. The "success/failed" messages will replace the HTML markup in the form, so they need to include their own HTML markup to display properly:

(n413register.php)
...
    if(isset($_SESSION["user_id"])){
        $messages["status"] = "1";
        $messages["success"] = '<h3>You are now Registered and Logged In.</h3>';
    }else{
        $messages["failed"] = '<h3>The Registration was not successful.</h3>
        <div class="col-12 text-center"><a href="register.php"><button type="button" class="btn btn-primary mt-5">Try Again</button></a></div>';
    }            
...

Next, we will encode the $messages array with JSON format and send it to the browser.

What is JSON? JSON is a data exchange format, based on JavaScript Object Notation. It uses the Javascript syntax to encode structured data, such as arrays, objects, and combinations of those structures. It is extremely useful and used in many programming languages, but it is especially helpful when passing data between servers and browsers. JSON uses quotes and escape methods to handle syntax complexities and special characters, and is therefore extremely versatile. A good JSON encoder can automatically generate well-formed JSON from complex data structures, and the JSON decoder in the browser can transform the JSON into complex Javascript objects. This avoids the need to do anything directly with JSON, which reduces the opportunity for errors.

We are working toward a script that returns JSON-formatted error messages as a part of an AJAX call, but for the sake of testing, make a version that will display the JSON version of the messages in a browser. All we want to do for now is test, so start with the "include" for "head.php". This is just so we can navigate while testing, and will be removed later. Add a line to display the $messages array, formatted as JSON just before the closing PHP tag (see below):

(n413register.php)
...
    include("head.php");
    echo json_encode($messages);
?>

The registration system will function now, and you should try it in your browser. There is still more to do, but this will work well enough to see. If you submit a registration, you should see a JSON representation of the messages. The "success/fail" messages will look strange because the browser will try to render them as HTML markup. If you have a look with the Web Inspector, you will see them with the JSON formatting, including the escape characters used to encode quotes, slashes, etc.

Have a look at the users_hash table in the database. You should be able to see any encrypted passwords from successful registrations when you view the database table.

The full script (as we have it now) looks like this:

(n413register.php)
<?php
    include("n413connect.php");
    
    function sanitize($item){
            global $link;
            $item = html_entity_decode($item);
            $item = trim($item);
            $item = stripslashes($item);
            $item = strip_tags($item);
            $item = mysqli_real_escape_string( $link, $item );
            return $item;
    }
    
    $messages = array();
    $messages["status"] = 0;
    $messages["errors"] = 0;
    $messages["username_length"] = "";
    $messages["username_exists"] = "";
    $messages["email_exists"] = "";
    $messages["email_validate"] = "";
    $messages["password_length"] = "";
    $messages["success"] = "";
    $messages["failed"] = "";
        
    $username = "";
    $email = "";
    $password = "";
        
    if(isset($_POST["username"])) { $username = $_POST["username"]; }
    $username = trim($username);
    if( strlen($username) < 5 ){
    	$messages["error"] = 1;
        $messages["username_length"] = "The Username must be at least 5 characters long.";
    }else{
        $username = sanitize($username);
    }
    
    if(isset($_POST["password"])) { $password = $_POST["password"]; }
    $password = trim($password);
    if( strlen($password) < 8 ){
    	$messages["error"] = 1;
        $messages["password_length"] = "The Password must be at least 8 characters long.";
    }else{
        $encrypted_password = password_hash($password, PASSWORD_DEFAULT);
        if($encrypted_password){ 
            $password = $encrypted_password; 
        }else{
            $messages["errors"] = 1;
            $messages["password_length"] = "Password encryption failed.  You cannot register at this time";
        }
    }
    
    if(isset($_POST["email"])) { $email = $_POST["email"]; }   
    if (filter_var($email, FILTER_VALIDATE_EMAIL)){
    	$email = sanitize($email);
    }else{
    	$messages["errors"] = 1;
    	$messages["email_validate"] = "There are problems with the e-mail address.  Please correct them.";
    }
    
    if( ! $messages["errors"]){
        $sql = "SELECT * FROM `users_hash` WHERE username = '".$username."'";
        $result = mysqli_query($link, $sql);
        if( mysqli_num_rows($result) > 0){
            $messages["errors"] = 1;
            $messages["username_exists"] = "This Username is already in use.  Please provide a different Username";
        }
        
        $sql = "SELECT * FROM `users_hash` WHERE email = '".$email."'";
        $result = mysqli_query($link, $sql);
        if( mysqli_num_rows($result) > 0){
            $messages["errors"] = 1;
            $messages["email_exists"] = "This E-mail address is already in use.  You cannot register another account for this E-mail.";
        }
    } //if( ! $messages["errors"])
    
    if( ! $messages["errors"]){
        $sql = "INSERT INTO `users_hash` (`id`, `username`, `email`, `password`, `role`) 
                VALUES (NULL, '".$username."', '".$email."', '".$password."', '0')";
        $result = mysqli_query($link, $sql); 
        
        $user_id = mysqli_insert_id($link);
        if($user_id){
            session_start();
            $_SESSION["user_id"] = $user_id;
            $_SESSION["role"] = "0";
        } 
    }  //if( ! $messages["errors"])  
    
    if(isset($_SESSION["user_id"])){
        $messages["status"] = "1";
        $messages["success"] = '<h3>You are now Registered and Logged In.</h3>';
    }else{
        $messages["failed"] = '<h3>The Registration was not successful.</h3>
        <div class="col-12 text-center"><a href="register.php"><button type="button" class="btn btn-primary mt-5">Try Again</button></a></div>';
    }  
    
    include("head.php");
    echo json_encode($messages);
?>

For the next step, we need to convert the registration form and the PHP script to AJAX. AJAX will allow us to keep the original registration form in the browser window, while we submit the data. The PHP script will do its work behind the scenes, then send back JSON data to the form. The JSON data will contain either success messages or error messages that we can display for the user.

AJAX. AJAX is an acronym that stands for "Asynchronous Javascript And XML". The AJAX process starts in the browser with an XMLHttpRequest object. This object a sends an http request to the server and listens for the server response. Once the response is received, the data is made available to the function that made the request.

jQuery provides a few different methods for using AJAX. The $.ajax() method is the most complex, and provides full access to the AJAX features offered by jQuery. There are "shorthand" methods, such as $.get() and $.post() that are easier to use and offer the most commonly needed AJAX features. We will use the $.post() method.

Registration Form Updates. Let's start with the browser and the Registration Form. The default action for a browser form is to load the page specified in the "action" attribute for the <form> tag when the "Submit" button is pressed. We will override this behavior and take control of the "submit" process with jQuery.

Open register.php. Add an id to the <form> tag so we can target it later. Then set the action attribute to empty quotes:

(register.php)
...
    <form id="register_form" method="POST" action="">
...

Submit Handler. Go to the $(document).ready() function and open a new line at the end of the function. We will write a "submit" handler here. The jQuery submit() function creates an event listener for the "submit" event. It takes the form's id as a selector, and an anonymous function as the argument. This function will become quite complex, so keep track of the brackets and parentheses carefully. A comment to mark the end of the submit handler will help you keep it straight. Start with this:

(register.php)
...
    $(document).ready(function(){ 
            document.title = page_title;
            navbar_update(this_page);
            
            $("#register_form").submit(function(){}); //submit
            
        }); //document.ready
...

PreventDefault(). The anonymous function will receive the "submit" event as an argument, so placing a variable in the argument parentheses will provide a reference to the submit event. We can use this to cancel the normal browser submit behavior with the preventDefault() method, as shown:

(register.php)
...
            $("#register_form").submit(function(event){
                    event.preventDefault();
                }); //submit
            
        }); //document.ready
...

Now we are in control of what happens when the Submit button is clicked. Add an alert or console.log after the preventDefault() line to test whether things are working as expected.

jQuery Post(). The jQuery post() function comes next. The post() function can take up to four arguments:

  • URL. The URL for the server script to call.
  • Data Object. A Javascript data object with the data to send to the server. In the case of a form, the jQuery function serialize() will produce a data object containing all the name-value pairs of the form's inputs that are not disabled.
  • Callback Function. An anonymous function, which will receive the server's response as an argument, and will run when the server response is received.
  • Data Type. A string that indicates how the server response should be interpreted. If the string is "text" or "html", the server's response will be treated as a text literal. If the string is "json", the JSON is rendered as Javascript and the callback function's argument can be used as a Javascript variable.

Only the URL argument is required. In that case, the server script would be called, but with no data. If only the URL and the data object are supplied, the server script would handle the data, but nothing would happen with the server response (if any). If a callback function is defined, the call back function will use the server response to perform some action in the browser. The data is interpreted as specified by the "data type" string, or by jQuery will try to guess the data type if nothing is specified.

Let's write a post() function that calls n413register.php and display the data that comes back with an alert:
(register.php)
...            
    $("#register_form").submit(function(event){
            event.preventDefault();
            $.post( "n413register.php",
                    $("#register_form").serialize(),
                    function(data){
                        alert(data);
                    },
                    "text"
            ); //post
        }); //submit
...

If you try this in a browser, you should see the HTML markup that n413register.php would've sent to the browser as a page. In fact, this is the output of the "include" for head.php. Instead of loading the markup in the browser window, the markup has been sent to the callback function.

AJAX Data. Anything the server returns from an AJAX call will be AJAX data, and will not be rendered in the browser, so you can never load a new page from an AJAX call. What you can do is use the callback function to rewrite page elements -- even the <body> tag -- or, you can do anything from inside the callback function you would expect, including reloading a new location.

We could have n413register.php send a message like "success" of "failed", then use the callback function to load a page that tells the user the result. Or we can have n413register.php send back the message we want to display and rewrite an element on the page with the message. Let's do the second approach.

But first, here is the complete version of the current register.php script in case you need to catch up:

(register.php)
<?php
    include("head.php");
?>
<style>
    .error_msg { display:none;color:#C00; }
</style>

<div class="container-fluid">
    <div id="headline" class="row  mt-5">
        <div class="col-12 text-center">
            <h2>Full Stack Amp Jam Registration</h2>
        </div> <!-- /col-12 -->
    </div> <!-- /row -->
    <form id="register_form" method="POST" action="">
    <div class="row mt-5">
        <div class="col-4"></div>  <!-- spacer -->
        <div id="form-container" class="col-4">
            <div>User Name: <input type="text" id="username" name="username" class="form-control" value="" placeholder="Enter User Name" required/></div>
            <div id="username_length" class="error_msg"></div>
            <div id="username_exists" class="error_msg"></div>
            <div class="mt-3">E-mail: <input type="email" id="email" name="email" class="form-control" value="" placeholder="Enter E-mail" required/></div>
            <div id="email_exists" class="error_msg"></div>
            <div id="email_validate" class="error_msg"></div>
            <div class="mt-3">Password: <input type="password" id="password" name="password" class="form-control" value="" placeholder="Enter Password" required/></div>
            <div id="password_length" class="error_msg"></div>
            <div class="mt-5"><button type="submit" id="submit" class="btn btn-primary float-right">Submit</button></div>
        </div>  <!-- /#form-container -->
    </div>  <!-- /.row -->
</form>
</body>
<script>
    var this_page = "register";
    var page_title = 'AMP JAM Site | Register';
		
    $(document).ready(function(){ 
            document.title = page_title;
            navbar_update(this_page);
            
            $("#register_form").submit(function(event){
                event.preventDefault();
                $.post( "n413register.php",
                        $("#register_form").serialize(),
                        function(data){
                            alert(data);
                        },
                        "text"
                ); //post
            }); //submit
            
        }); //document.ready
</script>
</html>

Sending Simple AJAX Data. Return to n413register.php, and modify it so it sends back data we can use.

Delete the "include" statement for "head.php". Then comment out (//) the "echo" line for sending JSON. Next, add the lines you see below:

(n413register.php)
...    
    if(isset($_SESSION["user_id"])){
        echo $messages["success"];
    }else{
        echo $messages["failed"];
    }
 ?>

Now when you submit a registration, you will see this:

jQuery html(). Since the message is formatted in HTML, we can use jQuery's html() function to rewrite the form elements. Revise the callback function to replace the form-container element with the data that comes back from the server. Go back to "register.php" and comment out the "alert" line. Then add a jQuery selector for "#form-container" and pass it data through the html() function.

The callback function should look like this:

(register.php)
...
                $.post( "n413register.php",
                        $("#register_form").serialize(),
                        function(data){
                            $("#form-container").html(data);
                        },
                        "text"
                ); //post
...

Here is what you should see when you submit a new registration form:

The "failed" message, including the "Try Again" button, will display the same way.

Using JSON Data. Let's take advantage of the structured data available with JSON to use the full set of messages available from n413register.php.

Go back to n413register.php, and delete the last lines added to echo the "success/failed" messages. Be sure to leave the lines that place the "success/failed" message strings in the $messages array. Now remove the comment slashes from the echo command.

JSON Formatting. Instead of echo-ing out the user feedback messages directly, they are put into the $messages array with the keys "success" or "failed". A "status" item is also updated to indicate whether the log-in process worked. The PHP json_encode() method will take the $messages array and convert it into JSON format. The echo command now sends the entire JSON string. This allows structured data to be sent to the browser, where it will be decoded into a Javascript variable. There is never a need to write or even see the JSON-formatted data that is passed between the server and the browser. Everything is handled automatically. However, if you want to see the JSON data, you can use the "Network" tab in the browser's Web Inspector to have a look at everything passing between the server and the browser in either direction.

Update jQuery post() for JSON. Jump back over to register.php and go to the post()function. The "data type" argument should be changed from "text" to "json". Then, in the callback function, create an "if-else" structure that checks the status and error states, as shown:

(register.php)
...
    $.post( "n413register.php",
        $("#register_form").serialize(),
        function(data){
            if(data.status){
                $("#form-container").html(data.success);
            }else{
                if(data.errors){
                    // handle error messages here
                }else{
                    $("#form-container").html(data.failed);  //registration failed, but without errors
                } //if data.errors
            } //if data.status
        },
        "json"
    ); //post
...

This should handle the "success/failed" messages (if there are no errors), and set up the structure for the additional error messages. The keys in the PHP $messages array are converted to object properties in Javascript. If data.status is true, the success message will be displayed in place of the form fields. Otherwise, we check for whether there are errors (which we will handle in the next steps). If the status is true, the "success" message is displayed.

Try it in your browser now.

Write the code for placing the error messages in the proper <div> tags. A for-in loop is a good way to accomplish this. The for-in loop will step through all the keys in the data variable. We can use the keys to target the <div> tag we need, and then update the HTML and CSS. A switch structure makes it simple to exclude the keys we don't want to use:

(register.php)
...
    $.post( "n413register.php",
        $("#register_form").serialize(),
        function(data){
            if(data.status){
                $("#form-container").html(data.success);
            }else{
                if(data.errors){
                    // handle error messages here
                    for (var key in data){
                        switch(key){
                            case "status":
                            case "errors":
                            case "success":
                            case "failed":
                                break;
                            default:$("#"+key).html(data[key]);
                                    $("#"+key).css("display","block");
                                break;
                        } //switch
                    } //for-in
                }else{
                    $("#form-container").html(data.failed);  //registration failed, but without errors
                } //if data.errors
            } //if data.status
        },
        "json"
    ); //post
...

Try this in the browser.

If there are errors, you should see an error message like the one here:

Test all the error checking functions in a browser. The error you see to the right is an email address that Bootstrap lets through, but PHP catches.

Log-In. Password encryption has now broken the log-in system set up earlier. Users can register for accounts, but they will never be able to log-in. Open the existing n413auth.php from your project folder and look for the lines that get the log-in credentials from the $_POST array. You don't want to sanitize the password this time, so change the $password line to simply assign the $_POST value to the $password variable, as shown:

(n413auth.php)
...
    $username = "";
    $password = "";
        
    if(isset($_POST["username"])) { $username = sanitize($_POST["username"]); }
    if(isset($_POST["password"])) { $password = $_POST["password"]; }
...

Next, adapt the SQL query for the new table, and only SELECT for the username:

(n413auth.php)
...
    $sql= "SELECT * FROM `users_hash` 
           WHERE username = '".$username."'
           LIMIT 1";
    $result = mysqli_query($link, $sql);
    $row = mysqli_fetch_array($result, MYSQLI_BOTH);
...
Now we will use password_verify() to test the log-in password against the encrypted password. Use the log-in and the encrypted passwords for the arguments, and password_verify() will return a Boolean true/false. If the password passes the test, create the log-in:
(n413auth.php)
...
    if(password_verify($password, $row["password"])){
        session_start();
        $_SESSION["user_id"] = $row["id"];
        $_SESSION["role"] = $row["role"];
    }
...

Note: Watch for closing the second parentheses in the "if" statement.

Log-ins are now working for encrypted passwords. Give it a test in your browser.

There an issue with password_verify() that corresponds to the password_hash() problem on older PHP versions. The fix is the same. Use lib/password.php with this script, also.

AJAX Log-in Convert the log-in system to AJAX and JSON. Create a $messages array just after the sanitize() function:

(n413auth.php)
...
    $messages = array();
    $messages["status"] = 0;
    $messages["role"] = 0;
    $messages["success"] = "";
    $messages["failed"] = "";
...

Next, delete the "include" statement for head.php. Borrow (cut-paste) the last few lines of n413register.php and adapt the messages for the log-in operation. Add in the role for users with admin privileges, as shown here.

(n413auth.php)
...
    if(isset($_SESSION["user_id"])){
        $messages["status"] = "1";
        $messages["role"] = $_SESSION["role"];
        $messages["success"] = '<h3class="text-center">You are now Logged In.</h3>';
    }else{
        $messages["failed"] = '<h3class="text-center">The Log-in was not successful.</h3>
        <div class="col-12 text-center"><a href="login.php"><button type="button" class="btn btn-primary mt-5">Try Again</button></a></div>';
    }  
    
    echo json_encode($messages);
?>
...

Delete everything past the closing PHP tag. it should look like this:

(n413auth.php)
<?php
    include("n413connect.php");
    
    function sanitize($item){
        global $link;
        $item = html_entity_decode($item);
        $item = trim($item);
        $item = stripslashes($item);
        $item = strip_tags($item);
        $item = mysqli_real_escape_string( $link, $item );
        return $item;
    }
        
    $messages = array();
    $messages["status"] = 0;
    $messages["role"] = 0;
    $messages["success"] = "";
    $messages["failed"] = "";
    
    $username = "";
    $password = "";
        
    if(isset($_POST["username"])) { $username = sanitize($_POST["username"]); }
    if(isset($_POST["password"])) { $password = $_POST["password"]; }
    
    $sql= "SELECT * FROM `users_hash` 
           WHERE username = '".$username."'
           LIMIT 1";
    $result = mysqli_query($link, $sql);
    $row = mysqli_fetch_array($result, MYSQLI_BOTH);    

    if(password_verify($password, $row["password"])){
        session_start();
        $_SESSION["user_id"] = $row["id"];
        $_SESSION["role"] = $row["role"];
    } 
    
    if(isset($_SESSION["user_id"])){
        $messages["status"] = "1";
        $messages["role"] = $_SESSION["role"];
        $messages["success"] = '<h3 class="text-center">You are now Logged In.</h3>';
    }else{
        $messages["failed"] = '<h3 class="text-center">The Log-in was not successful.</h3>
        <div class="col-12 text-center"><a href="login.php"><button type="button" class="btn btn-primary mt-5">Try Again</button></a></div>';
    }  
    
    echo json_encode($messages);
?>

Log-ins will work with this file, but the browser will only show the JSON data. Update login.php to display the messages. Open login.php and give the <form> tag an id, then set the "action" attribute to empty quotes ("").

(login.php)
...
    <form id="login_form" method="POST" action="">
...

Move to the $(document).ready() function and add an event handler for the submit event:

(login.php)           
...
            $("#login_form").submit(function(event){
                event.preventDefault();
            }); //submit 
...

Then add the $.post() method:

(login.php)           
...
            $("#login_form").submit(function(event){
                event.preventDefault();
                $.post("n413auth.php",
                       $("#login_form").serialize(),
                       function(data){
                       	//handle messages here
                       },
                       "json"
                ); //post
            }); //submit 
...

The callback function should check the status, and write the proper message to the #form-container div:

(login.php)           
...
                       function(data){
                       	    //handle messages here
                            if(data.status){
                                $("#form-container").html(data.success);
                            }else{
                                $("#form-container").html(data.failed);
                            }
                           },
...

The PASSWORD Project

The PASSWORD Project is now complete. You now have the basics for an account-enabled web application with security features and password encryption. There are many more features you might want to investigate on your own, such as password reset features, admin account management (for setting privileges), and AJAX features for real-time exchanges between the web interface and the database.

These features form the infrastructure for making your creative ideas a reality, so dream a little and think about how you can make your great idea come to life!

You can open a completed version of the project in your browser here. You can log in as Groot. You know the password.

Here are completed versions of the scripts used for this project:

(n413connect.php)
<?php $dbhost = 'localhost:8889'; //XAMPP is 'localhost:3306' $dbuser = 'root'; $dbpwd = 'root'; //XAMPP password is '' $dbname = 'ampjam_db'; $link = mysqli_connect($dbhost, $dbuser, $dbpwd, $dbname); if (!$link) { die('Connect Error (' . mysqli_connect_errno() . ') '. mysqli_connect_error()); } ?>
(head.php)
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes"> <title>Full Stack Amp Jam Site Project</title> <link href="css/bootstrap.min.css" rel="stylesheet"> <script src="js/jquery-3.4.1.min.js" type="application/javascript"></script> <script src="js/bootstrap.min.js" type="application/javascript"></script> <script src="js/bootstrap.min.js.map" type="application/javascript"></script> <script> function navbar_update(this_page){ $("#"+this_page+"_item").addClass('active'); $("#"+this_page+"_link").append(' <span class="sr-only">(current)</span>'); } <script> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <a class="navbar-brand" href="index.php">AMP JAM Site</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li id="home_item" class="nav-item"> <a id="home_link" class="nav-link" href="index.php">Home</a> </li> <li id="list_item" class="nav-item"> <a id="list_link" class="nav-link" href="list.php">The List</a> </li> <li id="contact_item" class="nav-item"> <a id="contact_link" class="nav-link" href="form.php">Contact</a> </li> </ul> <ul id="right_navbar" class="navbar-nav ml-auto mr-5"> <?php session_start(); if(isset($_SESSION["user_id"])){ if($_SESSION["role"] > 0){ echo ' <li id="messages_item" class="nav-item"> <a id="messages_link" class="nav-link" href="messages.php">Messages</a> </li>'; } echo ' <li id="logout_item" class="nav-item"> <a id="logout_link" class="nav-link" href="logout.php">Log-Out</a> </li>'; }else{ echo ' <li id="register_item" class="nav-item"> <a id="register_link" class="nav-link" href="register.php">Register</a> </li> <li id="login_item" class="nav-item"> <a id="login_link" class="nav-link" href="login.php">Log-In</a> </li>'; } ?> </ul> </div> </nav>
(register.php)
<?php include("head.php"); ?> <style> .error_msg { display:none;color:#C00; } </style> <div class="container-fluid"> <div id="headline" class="row mt-5"> <div class="col-12 text-center"> <h2>Full Stack Amp Jam Registration</h2> </div> <!-- /col-12 --> </div> <!-- /row --> <form id="register_form" method="POST" action=""> <div class="row mt-5"> <div class="col-4"></div> <!-- spacer --> <div id="form-container" class="col-4"> <div>User Name: <input type="text" id="username" name="username" class="form-control" value="" placeholder="Enter User Name" required/></div> <div id="username_length" class="error_msg"></div> <div id="username_exists" class="error_msg"></div> <div class="mt-3">E-mail: <input type="email" id="email" name="email" class="form-control" value="" placeholder="Enter E-mail" required/></div> <div id="email_exists" class="error_msg"></div> <div id="email_validate" class="error_msg"></div> <div class="mt-3">Password: <input type="password" id="password" name="password" class="form-control" value="" placeholder="Enter Password" required/></div> <div id="password_length" class="error_msg"></div> <div class="mt-5"><button type="submit" id="submit" class="btn btn-primary float-right">Submit</button></div> </div> <!-- /#form-container --> </div> <!-- /.row --> </form> </body> <script> var this_page = "register"; var page_title = 'AMP JAM Site | Register'; $(document).ready(function(){ document.title = page_title; navbar_update(this_page); $("#register_form").submit(function(event){ event.preventDefault(); $.post( "n413register.php", $("#register_form").serialize(), function(data){ if(data.status){ $("#form-container").html(data.success); right_navbar_update(); }else{ if(data.errors){ // handle error messages here for (var key in data){ switch(key){ case "status": case "errors": case "success": case "failed": break; default: $("#"+key).html(data[key]); $("#"+key).css("display","block"); break; } //switch } //for-in }else{ $("#form-container").html(data.failed); //registration failed, but without errors } //if data.errors } //if data.status }, //callback function "json" ); //post }); //submit }); //document.ready function right_navbar_update(){ var html = '<li id="logout_item" class="nav-item">'+ '<a id="logout_link" class="nav-link" href="logout.php">Log-Out</a>'+ '</li>'; $("#right_navbar").html(html); } </script> </html>
(users_hash.sql)
-- phpMyAdmin SQL Dump -- version 4.8.3 -- https://www.phpmyadmin.net/ -- -- Host: localhost:8889 -- Generation Time: Apr 30, 2020 at 08:20 PM -- Server version: 5.7.23 -- PHP Version: 7.2.10 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */; -- -- Database: `ampjam_db` -- -- -------------------------------------------------------- -- -- Table structure for table `users_hash` -- CREATE TABLE `users_hash` ( `id` int(11) NOT NULL, `username` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `password` text NOT NULL, `role` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Dumping data for table `users_hash` -- INSERT INTO `users_hash` (`id`, `username`, `email`, `password`, `role`) VALUES (1, 'groot', 'groot@guardians.com', '$2y$10$CEc6KF7zaYXIPdQrsiILqu8ro9j2UpqPdKtZ3wz3xEQ5LpD626QCG', 1), (2, 'rocket', 'rocket@guardians.com', '$2y$10$xngf4UYeK7Pbupz5IE21hexBeuLLjXHcyZ3Kj90revx5.6rL8ffeW', 0); -- -- Indexes for dumped tables -- -- -- Indexes for table `users_hash` -- ALTER TABLE `users_hash` ADD PRIMARY KEY (`id`); -- -- AUTO_INCREMENT for dumped tables -- -- -- AUTO_INCREMENT for table `users_hash` -- ALTER TABLE `users_hash` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
(n413register.php)
<?php include("n413connect.php"); function sanitize($item){ global $link; $item = html_entity_decode($item); $item = trim($item); $item = stripslashes($item); $item = strip_tags($item); $item = mysqli_real_escape_string( $link, $item ); return $item; } $messages = array(); $messages["status"] = 0; $messages["errors"] = 0; $messages["username_length"] = ""; $messages["username_exists"] = ""; $messages["email_exists"] = ""; $messages["email_validate"] = ""; $messages["password_length"] = ""; $messages["success"] = ""; $messages["failed"] = ""; $username = ""; $email = ""; $password = ""; if(isset($_POST["username"])) { $username = $_POST["username"]; } $username = trim($username); if( strlen($username) < 5 ){ $messages["error"] = 1; $messages["username_length"] = "The Username must be at least 5 characters long."; }else{ $username = sanitize($username); } if(isset($_POST["password"])) { $password = $_POST["password"]; } $password = trim($password); if( strlen($password) < 8 ){ $messages["error"] = 1; $messages["password_length"] = "The Password must be at least 8 characters long."; }else{ $encrypted_password = password_hash($password, PASSWORD_DEFAULT); if($encrypted_password){ $password = $encrypted_password; }else{ $messages["errors"] = 1; $messages["password_length"] = "Password encryption failed. You cannot register at this time"; } } if(isset($_POST["email"])) { $email = $_POST["email"]; } if (filter_var($email, FILTER_VALIDATE_EMAIL)){ $email = sanitize($email); }else{ $messages["errors"] = 1; $messages["email_validate"] = "There are problems with the e-mail address. Please correct them."; } if( ! $messages["errors"]){ $sql = "SELECT * FROM `users_hash` WHERE username = '".$username."'"; $result = mysqli_query($link, $sql); if( mysqli_num_rows($result) > 0){ $messages["errors"] = 1; $messages["username_exists"] = "This Username is already in use. Please provide a different Username"; } $sql = "SELECT * FROM `users_hash` WHERE email = '".$email."'"; $result = mysqli_query($link, $sql); if( mysqli_num_rows($result) > 0){ $messages["errors"] = 1; $messages["email_exists"] = "This E-mail address is already in use. You cannot register another account for this E-mail."; } } //if( ! $messages["errors"]) if( ! $messages["errors"]){ $sql = "INSERT INTO `users_hash` (`id`, `username`, `email`, `password`, `role`) VALUES (NULL, '".$username."', '".$email."', '".$password."', '0')"; $result = mysqli_query($link, $sql); $user_id = mysqli_insert_id($link); if($user_id){ session_start(); $_SESSION["user_id"] = $user_id; $_SESSION["role"] = "0"; } } //if( ! $messages["errors"]) if(isset($_SESSION["user_id"])){ $messages["status"] = "1"; $messages["success"] = '<h3>You are now Registered and Logged In.</h3>'; }else{ $messages["failed"] = '<h3>The Registration was not successful.</h3> <div class="col-12 text-center"><a href="register.php"><button type="button" class="btn btn-primary mt-5">Try Again</button></a></div>'; } echo json_encode($messages); ?>
(n413auth.php)
<?php include("n413connect.php"); function sanitize($item){ global $link; $item = html_entity_decode($item); $item = trim($item); $item = stripslashes($item); $item = strip_tags($item); $item = mysqli_real_escape_string( $link, $item ); return $item; } $messages = array(); $messages["status"] = 0; $messages["role"] = 0; $messages["success"] = ""; $messages["failed"] = ""; $username = ""; $password = ""; if(isset($_POST["username"])) { $username = sanitize($_POST["username"]); } if(isset($_POST["password"])) { $password = $_POST["password"]; } $sql= "SELECT * FROM `users_hash` WHERE username = '".$username."' LIMIT 1"; $result = mysqli_query($link, $sql); $row = mysqli_fetch_array($result, MYSQLI_BOTH); if(password_verify($password, $row["password"])){ session_start(); $_SESSION["user_id"] = $row["id"]; $_SESSION["role"] = $row["role"]; } if(isset($_SESSION["user_id"])){ $messages["status"] = "1"; $messages["role"] = $_SESSION["role"]; $messages["success"] = '<h3 class="text-center">You are now Logged In.</h3>'; }else{ $messages["failed"] = '<h3 class="text-center">The Log-in was not successful.</h3> <div class="col-12 text-center"><a href="login.php"><button type="button" class="btn btn-primary mt-5">Try Again</button></a></div>'; } echo json_encode($messages); ?>
(login.php)
<?php include("head.php"); ?> <div class="container-fluid"> <div id="headline" class="row mt-5"> <div class="col-12 text-center"> <h2>Full Stack Amp Jam Log-in</h2> </div> <!-- /col-12 --> </div> <!-- /row --> <form id="login_form" method="POST" action=""> <div class="row mt-5"> <div class="col-4"></div> <!-- spacer --> <div id="form-container" class="col-4"> User Name: <input type="text" id="username" name="username" class="form-control" value="" placeholder="Enter User Name" required/><br/> Password: <input type="password" id="password" name="password" class="form-control" value="" placeholder="Enter Password" required/><br/> <button type="submit" id="submit" class="btn btn-primary float-right">Submit</button> </div> <!-- /#form-container --> </div> <!-- /.row --> </form> </body> <script> var this_page = "login"; var page_title = 'AMP JAM Site | Login'; $(document).ready(function(){ document.title = page_title; navbar_update(this_page); $("#login_form").submit(function(event){ event.preventDefault(); $.post("n413auth.php", $("#login_form").serialize(), function(data){ //handle messages here if(data.status){ $("#form-container").html(data.success); right_navbar_update(data.role); }else{ $("#form-container").html(data.failed); } }, "json" ); //post }); //submit }); //document.ready function right_navbar_update(role){ var html = ""; if (role > 0) { html = '<li id="messages_item" class="nav-item">'+ '<a id="messages_link" class="nav-link" href="messages.php">Messages</a>'+ '</li>'; } html += '<li id="logout_item" class="nav-item">'+ '<a id="logout_link" class="nav-link" href="logout.php">Log-Out</a>'+ '</li>'; $("#right_navbar").html(html); } </script> </html>

If you want to download completed versions of the PHP scripts and image files, you can find them here.