Page 1 of 28 12311 ... Last
  • Jump to page:
    #1
  1. No Profile Picture
    Lost in code
    Devshed Supreme Being (6500+ posts)

    Join Date
    Dec 2004
    Posts
    8,317
    Rep Power
    7170

    How to program a basic but secure login system using PHP and MySQL [Updated 01/09/13]


    About this tutorial
    This tutorial is intended to explain how to design and build the foundation of a secure user authentication system using PHP's PDO library with a MySQL database. Each security measure is explained and justified, and many contain references to additional information should you wish to explore the subject in more depth. This article highlights insecure practices commonly found in code written by beginning PHP programmers who are simply unaware of how to write secure code.

    Note: I have run out of characters in this first post, so the tutorial continues in my second post.

    Target audience and prerequisites
    This tutorial is aimed at programmers with a basic knowledge of PHP and MySQL. It assumes that you already have:
    * A web server with PHP
    * A MySQL database server with a MySQL database created
    * Login details for the MySQL server
    * A basic understanding of general programming concepts and PHP syntax
    * A basic understanding of HTML and HTML forms
    * A basic conceptual understanding of the purpose of a database

    Topics on which this tutorial touches

    Security topics
    * How to prevent SQL injection exploits when using user-supplied data in a SQL query
    * How to prevent XSS attacks when displaying user-supplied data on a web page
    * How to securely store passwords in a database
    * How to securely redirect a user to another web page

    Database interaction topics
    * How to connect to a MySQL database using PHP's PDO library
    * How to insert a new row of data in the database (INSERT query)
    * How to update an existing row of data in the database (UPDATE query)
    * How to fetch a list of data from the database and display it in a table (SELECT query)
    * How to check whether a particular value already exists in the database

    General PHP topics
    * How to use PHP sessions to track a logged-in user
    * How to properly handle non-ASCII characters using UTF-8
    * How to avoid problems with PHP's Magic Quotes feature

    Login system specific topics
    * How to check whether a user is logged in or not, and force them to be logged in to view a particular page
    * How to build a login form, registration form, account details editing form and memberlist page

    The code

    First, you will need to create a database table with the following structure:
    Code:
    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;
    common.php: This file contains code that is shared between the different parts of your login system.
    PHP Code:
    <?php

        
    // These variables define the connection information for your MySQL database
        
    $username "dbusername";
        
    $password "dbpassword";
        
    $host "localhost";
        
    $dbname "dbname";

        
    // UTF-8 is a character encoding scheme that allows you to conveniently store
        // a wide varienty of special characters, like  or , in your database.
        // By passing the following $options array to the database connection code we
        // are telling the MySQL server that we want to communicate with it using UTF-8
        // See Wikipedia for more information on UTF-8:
        // http://en.wikipedia.org/wiki/UTF-8
        
    $options = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8');
        
        
    // A try/catch statement is a common method of error handling in object oriented code.
        // First, PHP executes the code within the try block.  If at any time it encounters an
        // error while executing that code, it stops immediately and jumps down to the
        // catch block.  For more detailed information on exceptions and try/catch blocks:
        // http://us2.php.net/manual/en/language.exceptions.php
        
    try
        {
            
    // This statement opens a connection to your database using the PDO library
            // PDO is designed to provide a flexible interface between PHP and many
            // different types of database servers.  For more information on PDO:
            // http://us2.php.net/manual/en/class.pdo.php
            
    $db = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8"$username$password$options);
        }
        catch(
    PDOException $ex)
        {
            
    // If an error occurs while opening a connection to your database, it will
            // be trapped here.  The script will output an error and stop executing.
            // Note: On a production website, you should not output $ex->getMessage().
            // It may provide an attacker with helpful information about your code
            // (like your database username and password).
            
    die("Failed to connect to the database: " $ex->getMessage());
        }
        
        
    // This statement configures PDO to throw an exception when it encounters
        // an error.  This allows us to use try/catch blocks to trap database errors.
        
    $db->setAttribute(PDO::ATTR_ERRMODEPDO::ERRMODE_EXCEPTION);
        
        
    // This statement configures PDO to return database rows from your database using an associative
        // array.  This means the array will have string indexes, where the string value
        // represents the name of the column in your database.
        
    $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODEPDO::FETCH_ASSOC);
        
        
    // This block of code is used to undo magic quotes.  Magic quotes are a terrible
        // feature that was removed from PHP as of PHP 5.4.  However, older installations
        // of PHP may still have magic quotes enabled and this code is necessary to
        // prevent them from causing problems.  For more information on magic quotes:
        // http://php.net/manual/en/security.magicquotes.php
        
    if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())
        {
            function 
    undo_magic_quotes_gpc(&$array)
            {
                foreach(
    $array as &$value)
                {
                    if(
    is_array($value))
                    {
                        
    undo_magic_quotes_gpc($value);
                    }
                    else
                    {
                        
    $value stripslashes($value);
                    }
                }
            }
        
            
    undo_magic_quotes_gpc($_POST);
            
    undo_magic_quotes_gpc($_GET);
            
    undo_magic_quotes_gpc($_COOKIE);
        }
        
        
    // This tells the web browser that your content is encoded using UTF-8
        // and that it should submit content back to you using UTF-8
        
    header('Content-Type: text/html; charset=utf-8');
        
        
    // This initializes a session.  Sessions are used to store information about
        // a visitor from one web page visit to the next.  Unlike a cookie, the information is
        // stored on the server-side and cannot be modified by the visitor.  However,
        // note that in most cases sessions do still use cookies and require the visitor
        // to have cookies enabled.  For more information about sessions:
        // http://us.php.net/manual/en/book.session.php
        
    session_start();

        
    // Note that it is a good practice to NOT end your PHP files with a closing PHP tag.
        // This prevents trailing newlines on the file from being included in your output,
        // which can cause problems with redirecting users.
    register.php: This file allows you to create a user account.
    PHP Code:
    <?php

        
    // First we execute our common code to connection to the database and start the session
        
    require("common.php");
        
        
    // This if statement checks to determine whether the registration form has been submitted
        // If it has, then the registration code is run, otherwise the form is displayed
        
    if(!empty($_POST))
        {
            
    // Ensure that the user has entered a non-empty username
            
    if(empty($_POST['username']))
            {
                
    // Note that die() is generally a terrible way of handling user errors
                // like this.  It is much better to display the error with the form
                // and allow the user to correct their mistake.  However, that is an
                // exercise for you to implement yourself.
                
    die("Please enter a username.");
            }
            
            
    // Ensure that the user has entered a non-empty password
            
    if(empty($_POST['password']))
            {
                die(
    "Please enter a password.");
            }
            
            
    // Make sure the user entered a valid E-Mail address
            // filter_var is a useful PHP function for validating form input, see:
            // http://us.php.net/manual/en/function.filter-var.php
            // http://us.php.net/manual/en/filter.filters.php
            
    if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL))
            {
                die(
    "Invalid E-Mail Address");
            }
            
            
    // We will use this SQL query to see whether the username entered by the
            // user is already in use.  A SELECT query is used to retrieve data from the database.
            // :username is a special token, we will substitute a real value in its place when
            // we execute the query.
            
    $query "
                SELECT
                    1
                FROM users
                WHERE
                    username = :username
            "
    ;
            
            
    // This contains the definitions for any special tokens that we place in
            // our SQL query.  In this case, we are defining a value for the token
            // :username.  It is possible to insert $_POST['username'] directly into
            // your $query string; however doing so is very insecure and opens your
            // code up to SQL injection exploits.  Using tokens prevents this.
            // For more information on SQL injections, see Wikipedia:
            // http://en.wikipedia.org/wiki/SQL_Injection
            
    $query_params = array(
                
    ':username' => $_POST['username']
            );
            
            try
            {
                
    // These two statements run the query against your database table.
                
    $stmt $db->prepare($query);
                
    $result $stmt->execute($query_params);
            }
            catch(
    PDOException $ex)
            {
                
    // Note: On a production website, you should not output $ex->getMessage().
                // It may provide an attacker with helpful information about your code. 
                
    die("Failed to run query: " $ex->getMessage());
            }
            
            
    // The fetch() method returns an array representing the "next" row from
            // the selected results, or false if there are no more rows to fetch.
            
    $row $stmt->fetch();
            
            
    // If a row was returned, then we know a matching username was found in
            // the database already and we should not allow the user to continue.
            
    if($row)
            {
                die(
    "This username is already in use");
            }
            
            
    // Now we perform the same type of check for the email address, in order
            // to ensure that it is unique.
            
    $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");
            }
            
            
    // An INSERT query is used to add new rows to a database table.
            // Again, we are using special tokens (technically called parameters) to
            // protect against SQL injection attacks.
            
    $query "
                INSERT INTO users (
                    username,
                    password,
                    salt,
                    email
                ) VALUES (
                    :username,
                    :password,
                    :salt,
                    :email
                )
            "
    ;
            
            
    // A salt is randomly generated here to protect again brute force attacks
            // and rainbow table attacks.  The following statement generates a hex
            // representation of an 8 byte salt.  Representing this in hex provides
            // no additional security, but makes it easier for humans to read.
            // For more information:
            // http://en.wikipedia.org/wiki/Salt_%28cryptography%29
            // http://en.wikipedia.org/wiki/Brute-force_attack
            // http://en.wikipedia.org/wiki/Rainbow_table
            
    $salt dechex(mt_rand(02147483647)) . dechex(mt_rand(02147483647));
            
            
    // This hashes the password with the salt so that it can be stored securely
            // in your database.  The output of this next statement is a 64 byte hex
            // string representing the 32 byte sha256 hash of the password.  The original
            // password cannot be recovered from the hash.  For more information:
            // http://en.wikipedia.org/wiki/Cryptographic_hash_function
            
    $password hash('sha256'$_POST['password'] . $salt);
            
            
    // Next we hash the hash value 65536 more times.  The purpose of this is to
            // protect against brute force attacks.  Now an attacker must compute the hash 65537
            // times for each guess they make against a password, whereas if the password
            // were hashed only once the attacker would have been able to make 65537 different 
            // guesses in the same amount of time instead of only one.
            
    for($round 0$round 65536$round++)
            {
                
    $password hash('sha256'$password $salt);
            }
            
            
    // Here we prepare our tokens for insertion into the SQL query.  We do not
            // store the original password; only the hashed version of it.  We do store
            // the salt (in its plaintext form; this is not a security risk).
            
    $query_params = array(
                
    ':username' => $_POST['username'],
                
    ':password' => $password,
                
    ':salt' => $salt,
                
    ':email' => $_POST['email']
            );
            
            try
            {
                
    // Execute the query to create the user
                
    $stmt $db->prepare($query);
                
    $result $stmt->execute($query_params);
            }
            catch(
    PDOException $ex)
            {
                
    // Note: On a production website, you should not output $ex->getMessage().
                // It may provide an attacker with helpful information about your code. 
                
    die("Failed to run query: " $ex->getMessage());
            }
            
            
    // This redirects the user back to the login page after they register
            
    header("Location: login.php");
            
            
    // Calling die or exit after performing a redirect using the header function
            // is critical.  The rest of your PHP script will continue to execute and
            // will be sent to the user if you do not die or exit.
            
    die("Redirecting to login.php");
        }
        
    ?>
    <h1>Register</h1>
    <form action="register.php" method="post">
        Username:<br />
        <input type="text" name="username" value="" />
        <br /><br />
        E-Mail:<br />
        <input type="text" name="email" value="" />
        <br /><br />
        Password:<br />
        <input type="password" name="password" value="" />
        <br /><br />
        <input type="submit" value="Register" />
    </form>
    login.php: This file allows users to authenticate.
    PHP Code:
    <?php

        
    // First we execute our common code to connection to the database and start the session
        
    require("common.php");
        
        
    // This variable will be used to re-display the user's username to them in the
        // login form if they fail to enter the correct password.  It is initialized here
        // to an empty value, which will be shown if the user has not submitted the form.
        
    $submitted_username '';
        
        
    // This if statement checks to determine whether the login form has been submitted
        // If it has, then the login code is run, otherwise the form is displayed
        
    if(!empty($_POST))
        {
            
    // This query retreives the user's information from the database using
            // their username.
            
    $query "
                SELECT
                    id,
                    username,
                    password,
                    salt,
                    email
                FROM users
                WHERE
                    username = :username
            "
    ;
            
            
    // The parameter values
            
    $query_params = array(
                
    ':username' => $_POST['username']
            );
            
            try
            {
                
    // Execute the query against the database
                
    $stmt $db->prepare($query);
                
    $result $stmt->execute($query_params);
            }
            catch(
    PDOException $ex)
            {
                
    // Note: On a production website, you should not output $ex->getMessage().
                // It may provide an attacker with helpful information about your code. 
                
    die("Failed to run query: " $ex->getMessage());
            }
            
            
    // This variable tells us whether the user has successfully logged in or not.
            // We initialize it to false, assuming they have not.
            // If we determine that they have entered the right details, then we switch it to true.
            
    $login_ok false;
            
            
    // Retrieve the user data from the database.  If $row is false, then the username
            // they entered is not registered.
            
    $row $stmt->fetch();
            if(
    $row)
            {
                
    // Using the password submitted by the user and the salt stored in the database,
                // we now check to see whether the passwords match by hashing the submitted password
                // and comparing it to the hashed version already stored in the database.
                
    $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'])
                {
                    
    // If they do, then we flip this to true
                    
    $login_ok true;
                }
            }
            
            
    // If the user logged in successfully, then we send them to the private members-only page
            // Otherwise, we display a login failed message and show the login form again
            
    if($login_ok)
            {
                
    // Here I am preparing to store the $row array into the $_SESSION by
                // removing the salt and password values from it.  Although $_SESSION is
                // stored on the server-side, there is no reason to store sensitive values
                // in it unless you have to.  Thus, it is best practice to remove these
                // sensitive values first.
                
    unset($row['salt']);
                unset(
    $row['password']);
                
                
    // This stores the user's data into the session at the index 'user'.
                // We will check this index on the private members-only page to determine whether
                // or not the user is logged in.  We can also use it to retrieve
                // the user's details.
                
    $_SESSION['user'] = $row;
                
                
    // Redirect the user to the private members-only page.
                
    header("Location: private.php");
                die(
    "Redirecting to: private.php");
            }
            else
            {
                
    // Tell the user they failed
                
    print("Login Failed.");
                
                
    // Show them their username again so all they have to do is enter a new
                // password.  The use of htmlentities prevents XSS attacks.  You should
                // always use htmlentities on user submitted values before displaying them
                // to any users (including the user that submitted them).  For more information:
                // http://en.wikipedia.org/wiki/XSS_attack
                
    $submitted_username htmlentities($_POST['username'], ENT_QUOTES'UTF-8');
            }
        }
        
    ?>
    <h1>Login</h1>
    <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" value="Login" />
    </form>
    <a href="register.php">Register</a>
    private.php: This is an example of a page that you might protect with your login system.
    PHP Code:
    <?php

        
    // First we execute our common code to connection to the database and start the session
        
    require("common.php");
        
        
    // At the top of the page we check to see whether the user is logged in or not
        
    if(empty($_SESSION['user']))
        {
            
    // If they are not, we redirect them to the login page.
            
    header("Location: login.php");
            
            
    // Remember that this die statement is absolutely critical.  Without it,
            // people can view your members-only content without logging in.
            
    die("Redirecting to login.php");
        }
        
        
    // Everything below this point in the file is secured by the login system
        
        // We can display the user's username to them by reading it from the session array.  Remember that because
        // a username is user submitted content we must use htmlentities on it before displaying it to the user.
    ?>
    Hello <?php echo htmlentities($_SESSION['user']['username'], ENT_QUOTES'UTF-8'); ?>, secret content!<br />
    <a href="memberlist.php">Memberlist</a><br />
    <a href="edit_account.php">Edit Account</a><br />
    <a href="logout.php">Logout</a>
    logout.php: Allows the user to log out
    PHP Code:
    <?php

        
    // First we execute our common code to connection to the database and start the session
        
    require("common.php");
        
        
    // We remove the user's data from the session
        
    unset($_SESSION['user']);
        
        
    // We redirect them to the login page
        
    header("Location: login.php");
        die(
    "Redirecting to: login.php");
    edit_account.php: This file is used by members to update their E-Mail address and password.
    PHP Code:
    <?php

        
    // First we execute our common code to connection to the database and start the session
        
    require("common.php");
        
        
    // At the top of the page we check to see whether the user is logged in or not
        
    if(empty($_SESSION['user']))
        {
            
    // If they are not, we redirect them to the login page.
            
    header("Location: login.php");
            
            
    // Remember that this die statement is absolutely critical.  Without it,
            // people can view your members-only content without logging in.
            
    die("Redirecting to login.php");
        }
        
        
    // This if statement checks to determine whether the edit form has been submitted
        // If it has, then the account updating code is run, otherwise the form is displayed
        
    if(!empty($_POST))
        {
            
    // Make sure the user entered a valid E-Mail address
            
    if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL))
            {
                die(
    "Invalid E-Mail Address");
            }
            
            
    // If the user is changing their E-Mail address, we need to make sure that
            // the new value does not conflict with a value that is already in the system.
            // If the user is not changing their E-Mail address this check is not needed.
            
    if($_POST['email'] != $_SESSION['user']['email'])
            {
                
    // Define our SQL query
                
    $query "
                    SELECT
                        1
                    FROM users
                    WHERE
                        email = :email
                "
    ;
                
                
    // Define our query parameter values
                
    $query_params = array(
                    
    ':email' => $_POST['email']
                );
                
                try
                {
                    
    // Execute the query
                    
    $stmt $db->prepare($query);
                    
    $result $stmt->execute($query_params);
                }
                catch(
    PDOException $ex)
                {
                    
    // Note: On a production website, you should not output $ex->getMessage().
                    // It may provide an attacker with helpful information about your code. 
                    
    die("Failed to run query: " $ex->getMessage());
                }
                
                
    // Retrieve results (if any)
                
    $row $stmt->fetch();
                if(
    $row)
                {
                    die(
    "This E-Mail address is already in use");
                }
            }
            
            
    // If the user entered a new password, we need to hash it and generate a fresh salt
            // for good measure.
            
    if(!empty($_POST['password']))
            {
                
    $salt dechex(mt_rand(02147483647)) . dechex(mt_rand(02147483647));
                
    $password hash('sha256'$_POST['password'] . $salt);
                for(
    $round 0$round 65536$round++)
                {
                    
    $password hash('sha256'$password $salt);
                }
            }
            else
            {
                
    // If the user did not enter a new password we will not update their old one.
                
    $password null;
                
    $salt null;
            }
            
            
    // Initial query parameter values
            
    $query_params = array(
                
    ':email' => $_POST['email'],
                
    ':user_id' => $_SESSION['user']['id'],
            );
            
            
    // If the user is changing their password, then we need parameter values
            // for the new password hash and salt too.
            
    if($password !== null)
            {
                
    $query_params[':password'] = $password;
                
    $query_params[':salt'] = $salt;
            }
            
            
    // Note how this is only first half of the necessary update query.  We will dynamically
            // construct the rest of it depending on whether or not the user is changing
            // their password.
            
    $query "
                UPDATE users
                SET
                    email = :email
            "
    ;
            
            
    // If the user is changing their password, then we extend the SQL query
            // to include the password and salt columns and parameter tokens too.
            
    if($password !== null)
            {
                
    $query .= "
                    , password = :password
                    , salt = :salt
                "
    ;
            }
            
            
    // Finally we finish the update query by specifying that we only wish
            // to update the one record with for the current user.
            
    $query .= "
                WHERE
                    id = :user_id
            "
    ;
            
            try
            {
                
    // Execute the query
                
    $stmt $db->prepare($query);
                
    $result $stmt->execute($query_params);
            }
            catch(
    PDOException $ex)
            {
                
    // Note: On a production website, you should not output $ex->getMessage().
                // It may provide an attacker with helpful information about your code. 
                
    die("Failed to run query: " $ex->getMessage());
            }
            
            
    // Now that the user's E-Mail address has changed, the data stored in the $_SESSION
            // array is stale; we need to update it so that it is accurate.
            
    $_SESSION['user']['email'] = $_POST['email'];
            
            
    // This redirects the user back to the members-only page after they register
            
    header("Location: private.php");
            
            
    // Calling die or exit after performing a redirect using the header function
            // is critical.  The rest of your PHP script will continue to execute and
            // will be sent to the user if you do not die or exit.
            
    die("Redirecting to private.php");
        }
        
    ?>
    <h1>Edit Account</h1>
    <form action="edit_account.php" method="post">
        Username:<br />
        <b><?php echo htmlentities($_SESSION['user']['username'], ENT_QUOTES'UTF-8'); ?></b>
        <br /><br />
        E-Mail Address:<br />
        <input type="text" name="email" value="<?php echo htmlentities($_SESSION['user']['email'], ENT_QUOTES'UTF-8'); ?>" />
        <br /><br />
        Password:<br />
        <input type="password" name="password" value="" /><br />
        <i>(leave blank if you do not want to change your password)</i>
        <br /><br />
        <input type="submit" value="Update Account" />
    </form>
    memberlist.php: This file allows members to view a list of all registered users.
    PHP Code:
    <?php

        
    // First we execute our common code to connection to the database and start the session
        
    require("common.php");
        
        
    // At the top of the page we check to see whether the user is logged in or not
        
    if(empty($_SESSION['user']))
        {
            
    // If they are not, we redirect them to the login page.
            
    header("Location: login.php");
            
            
    // Remember that this die statement is absolutely critical.  Without it,
            // people can view your members-only content without logging in.
            
    die("Redirecting to login.php");
        }
        
        
    // Everything below this point in the file is secured by the login system
        
        // We can retrieve a list of members from the database using a SELECT query.
        // In this case we do not have a WHERE clause because we want to select all
        // of the rows from the database table.
        
    $query "
            SELECT
                id,
                username,
                email
            FROM users
        "
    ;
        
        try
        {
            
    // These two statements run the query against your database table.
            
    $stmt $db->prepare($query);
            
    $stmt->execute();
        }
        catch(
    PDOException $ex)
        {
            
    // Note: On a production website, you should not output $ex->getMessage().
            // It may provide an attacker with helpful information about your code. 
            
    die("Failed to run query: " $ex->getMessage());
        }
            
        
    // Finally, we can retrieve all of the found rows into an array using fetchAll
        
    $rows $stmt->fetchAll();
    ?>
    <h1>Memberlist</h1>
    <table>
        <tr>
            <th>ID</th>
            <th>Username</th>
            <th>E-Mail Address</th>
        </tr>
        <?php foreach($rows as $row): ?>
            <tr>
                <td><?php echo $row['id']; ?></td> <!-- htmlentities is not needed here because $row['id'] is always an integer -->
                <td><?php echo htmlentities($row['username'], ENT_QUOTES'UTF-8'); ?></td>
                <td><?php echo htmlentities($row['email'], ENT_QUOTES'UTF-8'); ?></td>
            </tr>
        <?php endforeach; ?>
    </table>
    <a href="private.php">Go Back</a><br />

    Comments on this post

    • BarryG agrees : Clear, concise, and IT WORKS!
    • ptr2void agrees : Nice...now if we can just get it to the top of the Google search page.
    Last edited by E-Oreo; January 9th, 2013 at 06:01 PM.
    PHP FAQ

    Originally Posted by Spad
    Ah USB, the only rectangular connector where you have to make 3 attempts before you get it the right way around
  2. #2
  3. No Profile Picture
    Lost in code
    Devshed Supreme Being (6500+ posts)

    Join Date
    Dec 2004
    Posts
    8,317
    Rep Power
    7170
    Continued from my previous post

    Credits & Updates

    3/13/12
    * Initial tutorial

    10/6/12
    * Expanded the tutorial with new features requested by various people
    * Improved password security - suggested by Karl-Uwe Frank

    1/9/13
    * Fixed a security bug with the edit account feature that was added on 10/6 - discovered by Stuart Taylor
    Last edited by E-Oreo; January 9th, 2013 at 06:03 PM.
    PHP FAQ

    Originally Posted by Spad
    Ah USB, the only rectangular connector where you have to make 3 attempts before you get it the right way around
  4. #3
  5. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2012
    Posts
    153
    Rep Power
    3
    Thank-you. - This could definitely be very useful reference and study material for some upcoming projects.

    I have two questions, though:

    1: ) is InnoDB engine type preferable for PDO? - I converted a database to InnoDB a few months ago and found the performance, compared to MyISAM, to be very poor ...plus checking for, and repairing, corrupted data is different and not easy to understand or implement.

    2: ) is it necessary to apply UTF8 collation to each and every record, ie: does specifying such collation for the table not imply that the same collation should be used for all the records contained within that database?

    - I use UTF8 character type and collation, but had always assumed that everything within the table would follow suit unless I specified otherwise for something.
  6. #4
  7. No Profile Picture
    Lost in code
    Devshed Supreme Being (6500+ posts)

    Join Date
    Dec 2004
    Posts
    8,317
    Rep Power
    7170
    Originally Posted by C M Stafford
    1: ) is InnoDB engine type preferable for PDO? - I converted a database to InnoDB a few months ago and found the performance, compared to MyISAM, to be very poor ...plus checking for, and repairing, corrupted data is different and not easy to understand or implement.
    PDO is engine agnostic, it works equally well with MyISAM. The primary advantage of InnoDB is that it supports transactions and foreign key constraints, both of which are very important features for reliability and consistency. For most table uses, InnoDB should actually be faster than MyISAM, although there may be some tables for which MyISAM is faster. InnoDB adds a lot of new features, which have performance overhead, but it also adds a lot of performance enhancments like row-level locking instead of table-level locking. Repairing corrupted data is not something you should be having to do regularly at all; if it is, something is wrong with your database server.

    Originally Posted by C M Stafford
    2: ) is it necessary to apply UTF8 collation to each and every record, ie: does specifying such collation for the table not imply that the same collation should be used for all the records contained within that database?
    You can't specify UTF-8 collation at the record-level, but you can apply it at the column-level and set defaults at the table-level and database-level. The collation of the column does not have to be the same as the defaults at the table-level and database-level, and only the setting at the column-level affects how your data is stored.

    Comments on this post

    • C M Stafford agrees
    PHP FAQ

    Originally Posted by Spad
    Ah USB, the only rectangular connector where you have to make 3 attempts before you get it the right way around
  8. #5
  9. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Apr 2012
    Posts
    153
    Rep Power
    3
    Originally Posted by E-Oreo
    PDO is engine agnostic, it works equally well with MyISAM. The primary advantage of InnoDB is that it supports transactions and foreign key constraints, both of which are very important features for reliability and consistency. For most table uses, InnoDB should actually be faster than MyISAM, although there may be some tables for which MyISAM is faster. InnoDB adds a lot of new features, which have performance overhead, but it also adds a lot of performance enhancments like row-level locking instead of table-level locking. Repairing corrupted data is not something you should be having to do regularly at all; if it is, something is wrong with your database server.


    You can't specify UTF-8 collation at the record-level, but you can apply it at the column-level and set defaults at the table-level and database-level. The collation of the column does not have to be the same as the defaults at the table-level and database-level, and only the setting at the column-level affects how your data is stored.
    Thanks for the clarification. - My databases are usually fairly reliable; however care needs to be taken when using shared VPS setups. - The unmanaged solutions of some hosts are far better provisioned and configured than others
  10. #6
  11. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    May 2012
    Posts
    2
    Rep Power
    0

    Passing session variable to all pages


    Thanks for this simple and precise explanation of the login code.

    I have one issue though..

    The session created in the login and register forms are only valid for those pages,

    how do i carry and use the $_SESSION['user'] variable in all other pages.

    I was trying this in the header files:

    PHP Code:
    if (!empty ($_SESSION['user'])) {
        require(
    "usrcommon.php");
        echo 
    '<li>Welcome '.htmlentities($_SESSION['user']['username'], ENT_QUOTES'UTF-8').'!</li>'
    but the session variable $_SESSION['user'] is not getting passed except in the pages where the session was originally created.

    Thanks a lot.

    Looking forward for some help..strangely google was not of help
  12. #7
  13. Confused badger
    Devshed Beginner (1000 - 1499 posts)

    Join Date
    Mar 2009
    Location
    West Yorkshire
    Posts
    1,112
    Rep Power
    487
    Originally Posted by bha100710
    how do i carry and use the $_SESSION['user'] variable in all other pages.
    The $_SESSION variable should be there as long as your pages start with
    PHP Code:
    session_start(); 
  14. #8
  15. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    May 2012
    Posts
    2
    Rep Power
    0

    redirect users to referring/previous page after login


    Thanks got it working..

    Now struggling to redirect users to referring/previous page after login.

    Life's all struggles
  16. #9
  17. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Aug 2011
    Location
    Sydney Australia
    Posts
    184
    Rep Power
    84
    Originally Posted by bha100710
    Thanks got it working..

    Now struggling to redirect users to referring/previous page after login.

    Life's all struggles
    The referring page might be available in $_SERVER['HTTP_REFERER']

    http://au.php.net/manual/en/reserved.variables.server.php
  18. #10
  19. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    May 2012
    Posts
    78
    Rep Power
    0
    would those be your referring/previous pages? must be.

    keep track of them!

    add a 'breadcrumb' to the checking session conditional.
  20. #11
  21. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2011
    Posts
    51
    Rep Power
    56
    I must say it's quite a remarkable piece of software you're offering here. So no more excuses for storing un-salted password hashes.

    Originally Posted by E-Oreo
    Code:
    // This hashes the password with the salt so that it can be stored securely
    // in your database.  The output of this next statement is a 64 byte hex
    // string representing the 32 byte sha256 hash of the password.  The original
    // password cannot be recovered from the hash.  For more information:
    // http://en.wikipedia.org/wiki/Cryptographic_hash_function
    
    $password = hash('sha256', $_POST['password'] . $salt);
    I would suggest just a tiny but important improvement.

    Code:
    $salt = hash('sha256', $salt); // result to be stored in the database
    
    $hash = hash('sha256', $_POST['password'] . $salt);
    
    for ($i=0; $i<16384; i++){
       $hash = hash('sha256', $hash . $salt); 
    }
    
    $password = $hash
    Perhaps you might be interested in reading PBKDF2 (Password-Based Key Derivation Function 2) and Storing passwords in uncrackable form

    .
    .

    Comments on this post

    • E-Oreo agrees : that is a good improvement
  22. #12
  23. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Aug 2012
    Posts
    1
    Rep Power
    0
    Hi Chaps, ive just joined this forum to seek advice in this login system..
    I would like to retrieve data after login, however i cant figure what the actual $session'user' is. If i echo it out i get 'array'.

    I've see in the login.php

    Code:
    unset($row['salt']);
    unset($row['password']);
    $_SESSION['user'] = $row;
    ..but still none the wiser. Thanks in advance
  24. #13
  25. No Profile Picture
    Dazed&Confused
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2002
    Location
    Tempe, AZ
    Posts
    506
    Rep Power
    128
    I'm finding myself a little confused by password security, to be honest.

    I read the article above and all the "weaknesses" described seem based around a hacker knowing what the hashed/encrypted string is and brute forcing against that. But in my mind... if they've cracked your database or server in order to find what those strings are, you're compromised no matter how good your application security is.

    So long as the application itself authenticates based on only a username/password, all they need to do is brute force your password and submit to your very own form, sending their attempts through they very security logic meant to stop them.

    That said...

    It seems to offer better security to simply add logic to counter brute-force attacks (ie, lock an account after X failed attempts), require complex passwords, and--for really sensitive systems--require your users to change their password every so often.
  26. #14
  27. Transforming Moderator
    Devshed Supreme Being (6500+ posts)

    Join Date
    Mar 2007
    Location
    Washington, USA
    Posts
    14,113
    Rep Power
    9398
    Originally Posted by dmittner
    I read the article above and all the "weaknesses" described seem based around a hacker knowing what the hashed/encrypted string is and brute forcing against that. But in my mind... if they've cracked your database or server in order to find what those strings are, you're compromised no matter how good your application security is.
    Most of the time the hacker hasn't gained access to your database directly: they've found an injection point or an admin interface they can use, and through that can see the hashed passwords. Besides, if they have gained direct access to the database (or your server) you're screwed anyways.

    Originally Posted by dmittner
    So long as the application itself authenticates based on only a username/password, all they need to do is brute force your password
    Brute-forcing is one of the biggest reasons why your hashing algorithm should be very slow (relatively speaking). Most cited example would be bcrypt.

    Originally Posted by dmittner
    It seems to offer better security to simply add logic to counter brute-force attacks (ie, lock an account after X failed attempts), require complex passwords, and--for really sensitive systems--require your users to change their password every so often.
    You most certainly should do that. But it's not an either-or thing: enforce constraints on the user and the login process and use secure hashing/encryption practices.
  28. #15
  29. No Profile Picture
    Dazed&Confused
    Devshed Novice (500 - 999 posts)

    Join Date
    Jun 2002
    Location
    Tempe, AZ
    Posts
    506
    Rep Power
    128
    Originally Posted by requinix
    Most of the time the hacker hasn't gained access to your database directly: they've found an injection point or an admin interface they can use, and through that can see the hashed passwords. Besides, if they have gained direct access to the database (or your server) you're screwed anyways.
    But in the context of the original code, if they've gained enough access to get the hashed password, wouldn't they likely have gotten the user's salt, too? If they have both then they could still brute force it.

    It seems an application-specific salt (ie, NOT defined anywhere in the database) would be a better option. That would be more akin to re-hashing X amount of times; something only the application knows about. But then it would be of minimal value to use both; one or the other would suffice.

    You most certainly should do that. But it's not an either-or thing: enforce constraints on the user and the login process and use secure hashing/encryption practices.
    Secure practices would mean not exposing your database in any way. If they can't get the hash to begin with, they'll be at a loss. And if they *can* get the hash, chances are you've got a pretty big security issue elsewhere which would be of more importance to correct.
Page 1 of 28 12311 ... Last
  • Jump to page:

IMN logo majestic logo threadwatch logo seochat tools logo