Password-less authentication in PHP and MySQL

Previously we did password less authentication in Node JS and Mongo DB, in this article we will do it in PHP and MySQL.

Suppose you are trying to login to someone’s computer. You might open an incognito window in order to prevent the password from being saved in the browser. But what if he has installed a keylogger in his computer ? He can track your keystrokes and will know your email and password.

You can try typing your password from virtual keyboard, but what if he has also installed a screen recroding software that quitely records the screen ?

So the website developers must provide a way to secure their users from such vulnerabilities.

The best way is to allow users to login without entering their password. In-fact, there will be no password. User just have to enter his email address. We will send him a verification code. He needs to enter that code to verify.

That code will be auto-generated and will not be used again once user is logged-in. So even if some keylogging or screen recording software knows the verification code, it will be of no use. Because the code will not work next time someone tries with that user’s email address.

Create a Table

First I am going to create users table. If you already have one, you just need to add another column code in it.

<?php

$db_name = "test";
$username = "root";
$password = "";

$conn = new PDO("mysql:host=localhost;dbname=" . $db_name, $username, $password);

$sql = "CREATE TABLE IF NOT EXISTS users(
    id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
    email TEXT NULL,
    code TEXT NULL
)";

$result = $conn->prepare($sql);
$result->execute();

I am using PDO to prevent SQL injection. If you want to know more about PHP PDO, you can check our guide here. If you refresh the page now and check your phpMyAdmin, you will have users table created in your database.

users-table-password-less-authentication-php
users-table-password-less-authentication-php

Login Form

Next we need to create a form to ask for user’s email address. It will also have a submit button.

<form method="POST" action="index.php">
    <p>
        <input type="email" name="email" placeholder="Enter email" />
    </p>

    <input type="submit" value="Login" />
</form>

This will create an input field and a submit button.

Send email in PHP

Then we need to generate a random code and send an email with that verification code. For sending email, we will be using a library PHPMailer. You can download it and extract the folder in your project’s root directory.

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'PHPMailer/src/Exception.php';
require 'PHPMailer/src/PHPMailer.php';
require 'PHPMailer/src/SMTP.php';

function send_mail($to, $subject, $body)
{
    //Create an instance; passing `true` enables exceptions
    $mail = new PHPMailer(true);

    try {
        //Server settings
        $mail->SMTPDebug = 0;                      //Enable verbose debug output
        $mail->isSMTP();                                            //Send using SMTP
        $mail->Host       = 'mail.adnan-tech.com';                     //Set the SMTP server to send through
        $mail->SMTPAuth   = true;                                   //Enable SMTP authentication
        $mail->Username   = 'support@adnan-tech.com';                     //SMTP username
        $mail->Password   = '';                               //SMTP password
        $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;            //Enable implicit TLS encryption
        $mail->Port       = 465;                                    //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`

        //Recipients
        $mail->setFrom('support@adnan-tech.com', 'Adnan Afzal');
        $mail->addAddress($to);               //Name is optional

        //Content
        $mail->isHTML(true);                                  //Set email format to HTML
        $mail->Subject = $subject;
        $mail->Body    = $body;
        $mail->AltBody = $body;

        $mail->send();
        // echo 'Message has been sent';
    } catch (Exception $e) {
        // echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
    }
}

if ($_SERVER["REQUEST_METHOD"] == "POST")
{
    $email = $_POST["email"];

    $str = "qwertyuiopasdfghjklzxcvbnm1234567890";
    $code = "";

    for ($a = 1; $a <= 6; $a++)
    {
        $code .= $str[rand(0, strlen($str) - 1)];
    }

    $subject = "Login";
    $body = "Your verification code is " . $code;

    $sql = "SELECT * FROM users WHERE email = :email";
    $result = $conn->prepare($sql);
    $result->execute([
        ":email" => $email
    ]);
    $user = $result->fetch();

    if ($user == null)
    {
        $sql = "INSERT INTO users(email, code) VALUES (:email, :code)";
        $result = $conn->prepare($sql);
        $result->execute([
            ":email" => $email,
            ":code" => $code
        ]);

        send_mail($email, $subject, $body);

        header("Location: verify.php?email=" . $email);
        exit();
    }

    $sql = "UPDATE users SET code = :code WHERE email = :email";
    $result = $conn->prepare($sql);
    $result->execute([
        ":email" => $email,
        ":code" => $code
    ]);

    send_mail($email, $subject, $body);

    header("Location: verify.php?email=" . $email);
    exit();
}

Here, first I am generating a random 6 character code. Then I am setting the body of the email in a $body variable.

Then I am checking if the user already exists in the database. If not, then insert a new row in database. Sends an email and redirect the user to the verification page.

If user already exists, then I am simply updating his code column. Sends an email and redirect to a new page to verify the code.

Note: You need to enter your correct SMTP credentials (email and password) in order to send the email.

Verify the Code

Create a new file verify.php and inside it, first get the email from the URL. Then create a form with an hidden input field for email. One input field for user to enter the code sent at email, and a submit button.

<?php

    $db_name = "test";
    $username = "root";
    $password = "";

    $conn = new PDO("mysql:host=localhost;dbname=" . $db_name, $username, $password);
    $email = $_GET["email"] ?? "";

?>

<form method="POST" action="verify.php">
    <input type="hidden" name="email" value="<?php echo $email; ?>" />

    <p>
        <input type="text" name="code" placeholder="Enter your code here..." required />
    </p>

    <input type="submit" value="Login" />
</form>

We are creating a hidden input field so that it will be sent along with form. Now when the form submits, we need to check if user has provided correct code.

if ($_SERVER["REQUEST_METHOD"] == "POST")
{
    $email = $_POST["email"] ?? "";
    $code = $_POST["code"] ?? "";

    $sql = "SELECT * FROM users WHERE email = :email AND code = :code";
    $result = $conn->prepare($sql);
    $result->execute([
        ":email" => $email,
        ":code" => $code
    ]);
    $user = $result->fetch();

    if ($user == null)
    {
        die("Invalid code.");
    }

    $sql = "UPDATE users SET code = NULL WHERE id = :id";
    $result = $conn->prepare($sql);
    $result->execute([
        ":id" => $user["id"]
    ]);

    die("Logged-in");
}

If the user provides an in-valid code, then we are simply displaying him an error message. If he has provided the correct code, then we are setting his code column to NULL so it won’t be used again.

You can also try sending an AJAX and pass the code value as null, it still won’t be logged-in.

// for testing only

const ajax = new XMLHttpRequest()
ajax.open("POST", "verify.php", true)

const formData = new FormData()
formData.append("email", "support@adnan-tech.com")
formData.append("code", null)
ajax.send(formData)

So that’s it. That’s how you can add password-less authentication system in your application using PHP and MySQL. If you face any problem in following this, kindly do let me know.