How To Create Secure Registration Flow with PHP and Password Hashing

Written by thomaso | Published 2020/09/12
Tech Story Tags: php | mysql | backend | web-development | passwords | password-security | sql | security

TLDR How To Create Secure Registration Flow with PHP and Password Hashing? Building a secure user registration form with PHP seems like a scary task. Surprisingly, only a few steps and precautions, you can greatly reduce the chance of success for attacks. In this post, I will talk you through some basic but important steps to build a robust and secure registration form in PHP.Generate an activation key and have new users confirm their account registration via email. Use a random 15 character string to avoid any conflicts should a large number of users sign-up with your site.via the TL;DR App

Building a secure user registration form with PHP seems like a scary task. How do I protect myself from MySQL injection and other methods of hacking. Surprisingly, with only a few steps and precautions, you can greatly reduce the chance of success for attacks.
In this post, I will talk you through some basic but important steps to build a robust and secure registration form in PHP.
<?php

// Replace $user and $pass with strong values for production
$host = 'localhost';
$db   = 'new_db';
$user = 'root';
$pass = '';

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];
$dsn = "mysql:host=$host;dbname=$db";
try {
     $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
     throw new \PDOException($e->getMessage(), (int)$e->getCode());
}

?>
Establish your database connection using PDO. You can copy & paste the above and you're good to go.
Next, you need to grab the fields from the submitted registration form. In my case, I'm only asking the user to submit their first name, email, and password + confirmation.
<?php

// Get form field values
    $fname              = filter_input(INPUT_POST, 'fname', FILTER_SANITIZE_STRING);
    $email              = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
    $pwd                = filter_input(INPUT_POST, 'password');
    $pwd_confirm        = filter_input(INPUT_POST, 'confirm-password');

?>
Make sure to not use FILTER_SANITIZE_STRING on the password as you don't invalidate a user's password. Someone, especially tech-savvy, people, might like having something like '<script>' as part of their password.
<?php

$activation_key     = bin2hex(random_bytes(15));
$activation_link    = 'https://www.example.com/activate?id='.$activation_key.'&name='.$fname;

?>
Generate an activation key and have new users confirm their account registration via email. This will greatly reduce the number of spam accounts. In my case, I used
bin2hex
to generate a random 15 character string to avoid any conflicts should a large number of users sign-up with your site. I also added the first name of the user to the string, which I can then verify server-side.
<?php 

// Check if passwords match
    if ($pwd !== $pwd_confirm) {
        $errors[] = "Passwords don't match";
    }

    // Check if password is secure
    if (strlen($pwd) < 8) {
        $errors[] = "Password not long enough! Must be at least 8 characters long";
    }

    // Check if username equals password
    if ($fname === $pwd) {
        $errors[] = "Your name cannot be your password!";
    }

?>
I'm now checking for errors in the form submission. Note that the check whether the password is secure is not very sophisticated at this point. I recommend to introduce further checks, e.g. capital and non-capital letters, special characters etc. You can do this using a regular expression.
<?php 

// Check if email address exists in database
    $email_query = $pdo->prepare("SELECT count(1) FROM users WHERE email = :email");
    $email_query->bindParam(':email', $email);
    $email_query->execute();
    $email_found = $email_query->fetchColumn();
    if ($email_found) {
        $errors[] = "Your email address is associated with another account.";
    }

?>
I'm also checking whether the email address already exists in the database. When you
SELECT
or
INSERT INTO
values from/to your database, always use prepare statements and bind the parameters to avoid SQL injection.
<?php 

// If no errors, continue with user account creation
    if (!$errors)
    {   
        // Hash password
        $hashed_password = password_hash($pwd, PASSWORD_DEFAULT);

        // Create database entry
        $create_account = $pdo->prepare("INSERT INTO users (first_name,email,password, activation_key) VALUES (:fname, :email, :password, :activation_key)");
        $create_account->bindParam(':fname', $fname);
        $create_account->bindParam(':email', $email);
        $create_account->bindParam(':password', $hashed_password);
        $create_account->bindParam(':activation_key', $activation_key);
        $create_account->execute();

        // Send out activation email
        $to=$email;
        $subject="Activate your account";
        $from = 'no-reply@example.com';
        $body='Thank you for creating your account, '.$fname.'. Please click on the following link to activate your account: <a href="'.$activation_link.'">'.$activation_link.'</a>';
        $headers = "From:".$from;
        mail($to,$subject,$body,$headers);

        // Redirect user to the dashboard
        header("Location: /dashboard.php");
        exit;
    }

?>
If you've found no errors with the form submission, it's time to create a new account. To do this, you should first hash the submitted password. NEVER store plain-text passwords in your database. You are then going to prepare the statement, bind the parameters and execute to insert the values into the database.
Last but not least, you want to send out the activation email. In this example, I used PHP's mail() function but you might want to go with PHPMailer instead. Also, you might want to use
http_build_query()
to build the activation string in case the user's first name is not url-compliant, e.g. uses ampersands.
You can now exit the registration process and wait until the user activated his/her account or redirect them to the next page, e.g. dashboard. However, note that you probably want to initiate the session if you do go with a redirect.
That's it!
Thomaso

Published by HackerNoon on 2020/09/12