How secure is my PHP registration script? 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
&lt;?php

// User/pass only used locally
$host = 'localhost';
$db   = 'new_db';
$user = 'root';
$pass = '';

$options = [
    PDO::ATTR_ERRMODE            =&gt; PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE =&gt; PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   =&gt; false,
];
$dsn = &quot;mysql:host=$host;dbname=$db&quot;;
try {
     $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
     throw new \PDOException($e-&gt;getMessage(), (int)$e-&gt;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.'&amp;name='.$fname;

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

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

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

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


    // 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-&gt;prepare(&quot;INSERT INTO users (first_name,email,password, activation_key) VALUES (:fname, :email, :password, :activation_key)&quot;);
        $create_account-&gt;bindParam(':fname', $fname);
        $create_account-&gt;bindParam(':email', $email);
        $create_account-&gt;bindParam(':password', $hashed_password);
        $create_account-&gt;bindParam(':activation_key', $activation_key);
        $create_account-&gt;execute();

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

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



}

?&gt;

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[&quot;loggedin&quot;] = true;
$_SESSION[&quot;email&quot;] = $email; 

// Redirect user to the dashboard
header(&quot;Location: /dashboard.php&quot;);
exit;
You properly protect against SQL injection, that's good.
I worry about the filtering on the password. If I choose &lt;script&gt;' OR 1=1 &quot;&gt;&gt;&gt; as my password, the filtering should not reduce this to just ' OR 1=1 &quot;, just because some part of it looks like HTML tags. A password is not supposed to be displayed anywhere, it is not supposed to be stored in plain text anywhere, the only valid use is to feed it to password_validate and password_hash, and these can cope with arbitrary input (well, except that they stop processing at the first byte that is 0, that is, the byte value 0, not the digit '0'). When I enter a password, I expect this password to be used exactly, unmodified, unnormalized, just as I entered it.
I dimly remember that uniqid is not very unique at all. You should double-check the documentation whether that function provides enough randomness to protect against guessing attacks.
Your code is sending out a mail containing &lt;a href=&quot;...&quot;&gt;, but nowhere in the code do I see a Content-type: text/html; charset=UTF-8 header.
Your code is sending out malformed HTML. The URL contains a plain &amp;, which in HTML must be escaped as &amp;amp;. To do this, call htmlspecialchars($url). Most browsers accept this malformed HTML, but it's better do know how to correctly convert between plain text and HTML. Just to avoid cross-site scripting from the beginning.
What if the $fname contains an ampersand? Some companies have that in their name. I don't know of actual people with these names, but who knows? An ampersand would make the generated $activation_link invalid.