I coded a registration form and was wondering if anyone could give me feedback on whether I'm lacking any major security mechanisms. I've done loads of reading around the topic and feel it should be secure but expert feedback would be great.
Registration script
<?php
// User/pass only used locally
$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());
}
$errors = [];
if ( $_POST )
{
// 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', FILTER_SANITIZE_STRING);
$pwd_confirm = filter_input(INPUT_POST, 'confirm-password', FILTER_SANITIZE_STRING);
// Generate activation key
$activation_key = md5(uniqid(mt_rand(), true));
$activation_link = 'https://www.example.com/activate?id='.$activation_key.'&name='.$fname;
// 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!";
}
// Check if email address exists in database
$email_query = $pdo->prepare("SELECT * 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.";
}
// 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;
}
}
?>
I do want to use it on a live site at one point, so the end result should not just be a registration form that's somewhat secure but could actually be used in production.
EDIT
I did some more thinking and I guess before redirecting the newly registered user, I have to start a session to detect on the dashboard page whether the user is logged in. Is the following sufficient and secure to to add after I sent out the activation email?
session_start();
$_SESSION["loggedin"] = true;
$_SESSION["email"] = $email;
// Redirect user to the dashboard
header("Location: /dashboard.php");
exit;
$activation_key = md5(uniqid(mt_rand(), true));
- stop, just dobin2hex(random_bytes(15));
or something like that, it's much safer than uniqid() \$\endgroup\$ – hanshenrik Aug 3 '20 at 9:47