Next and previous links in PHP, MySQL

If you see a WordPress site, you will notice that at the bottom of the blog listing page, there will be links to next and previous pages.

They are useful to move the user to next or previous page.

If user is at the last page, then the next button will not be displayed.

Similarly, if user is at the first page, then the previous button will be hidden.

We are achieve this in PHP and MySQL. Following is the code to do that:

<?php

// Create a new PDO connection to the MySQL database
$pdo = new PDO("mysql:host=localhost; dbname=test", "root", "root", [
    PDO::ATTR_PERSISTENT => true // Enables persistent connection for better performance
]);

// Define the number of records to show per page
$per_page = 2;

// Get the current page number from the query string, default to 1 if not set
$page = $_GET["page"] ?? 1;

// Calculate the offset for the SQL query
$skip = ($page - 1) * $per_page;

// Fetch the users for the current page, ordered by ID in descending order
$sql = "SELECT * FROM users ORDER BY id DESC LIMIT $skip, $per_page";
$stmt = $pdo->prepare($sql); // Prepare the SQL statement
$stmt->execute([]); // Execute the query
$users = $stmt->fetchAll(PDO::FETCH_OBJ); // Fetch all results as objects

// Query to get the total number of users in the database
$sql = "SELECT COUNT(*) AS total_count FROM users";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_OBJ);
$total_count = $row->total_count ?? 0; // Get the total count or default to 0

// Calculate the total number of pages required
$total_pages = ceil($total_count / $per_page);

// Determine if there are more pages
$has_more_pages = ($page * $per_page) < $total_count;

// Initialize previous and next page variables
$previous_page = $next_page = null;

// Determine the previous page number
if ($page > 1)
{
    $previous_page = $page - 1;
}

// Determine the next page number
if ($page < $total_pages)
{
    $next_page = $page + 1;
}

?>

<!-- Loop through and display each user -->
<?php foreach ($users as $user): ?>
    <p><?php echo $user->name; ?></p>
<?php endforeach; ?>

<!-- Pagination controls -->
<?php if ($next_page > 0): ?>
    <a href="?page=<?php echo $next_page; ?>">Previous</a>
<?php endif; ?>

<?php if ($previous_page > 0): ?>
    <a href="?page=<?php echo $previous_page; ?>">Next</a>
<?php endif; ?>

Comments has been added with each line for explanation.

Even if you are working in any other language like Node.js and MongoDB, you can still achieve this because now you have the algorithm to do that.

We implemented this technique in one of our project: Multi-purpose platform in Node.js and MongoDB

From there, you will get the idea how you can render next and previous links if you are working in Node.js and MongoDB as backend and React as frontend.

Free tool to write API documentation – PHP, MySQL

I was looking for a tool that allows me to write API documentation so I can provide it to the frontend developers and also for myself. I checked many but didn’t find any that fits to all of my requirements. So I decided to create one of my own. Creating my own tool gives me flexibility to modify it as much as I want.

I also wanted it to share it with other developers who might be having problem in finding the tool to write documentation for their API. So I uploaded it on Github and it is available for anyone for free.

How to write API documentation

A tool is created in PHP and MySQL that allows developers to write API documentation, and this tool is available for free. You can create multiple sections to group the APIs based on modules. For example, user authentication, user posts, comments, replies can be separate sections.

To write each API, you need to tell:

  • The section where it goes.
  • The name of the endpoint. It can be the URL of API.
  • The method, it can be either GET or POST. But since you will have the code, you can add more methods as per your needs.
  • Add a little description about the API, for example what this API does.
  • Headers:
    • You need to tell the key of header, whether it is required or optional. And a little description about the header, for example, it’s possible values.
  • Parameters:
    • Parameters are usually send in the URL. You can define them along with their key, and value and whether they are optional or not.
  • Arguments:
    • For defining the arguments, you need to specify it’s type too. Whether it can be an integer, a string, boolean value or a file object.
  • Example request body. You can write the CURL code inside it to give an example.
  • Status codes and their responses.

I wrote a complete documentation of a project using this tool. You can check that from here.

Download

Assign shirt number to new cricketer – PHP PDO MySQL

Suppose you have a database where data of all players of any sports team is stored. Every player has a name and a shirt number. We will create a program that will generate a new number for new player. We will make sure the new number is not already assigned to any player. We will keep generating new numbers until a unique number is found.

First, create a table “players” in your database by running the following SQL query.

CREATE TABLE IF NOT EXISTS players2 (
    id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    shirt_number INTEGER(11) NOT NULL
);

Then, create a file named “assign-shirt-number-to-new-player.php” and write the following code in it:

// connect with database
$pdo = new PDO("mysql:host=localhost; dbname=test;", "root", "", [
    PDO::ATTR_PERSISTENT => true
]);

This will make a persistent connection with the database. We are using PHP PDO because it has built-in support for SQL injection. After that, you need to fetch all the players already saved in the database.

// fetch all the players from the database
$statement = $pdo->prepare("SELECT * FROM players");
$statement->execute([]);
$rows = $statement->fetchAll(PDO::FETCH_OBJ);

Then we will set the range of numbers in which we want to generate a new number.

// set number of range for shirt numbers
$number_range = 10;

This will generate numbers between 0 and 10. After that, we will create a recursive function that will keep generating a new number unitl a unique shirt number is found.

// recursive function to return the new shirt number
function get_code()
{
    // define global variables to access them inside function
    global $rows, $number_range;

    // if all the numbers are taken, then return -1
    if (count($rows) >= $number_range)
        return -1;

    // generate a random number
    $code = rand(0, $number_range);

    // check if it already exists in the database
    foreach ($rows as $row)
    {
        if ($row->shirt_number == $code) // if number already exists, then start recursion i.e. generate another number
        return get_code();
    }

    // if number not already exists, then return it
    return $code;
}

We need to call this function once to start checking for new unique shirt number.

// initial call to recursive function
$code = get_code();

There is a possibility that all the numbers from 0 to 10 are already taken, in this case, we will display an error message.

// if all numbers are taken, display an error
if ($code == -1)
{
    echo "<p>All numbers are taken.</p>";
    exit;
}

If everything worked fine, we will have a new unique shirt number in our $code variable. We can simply insert it in our database.

// name of new player
$name = "Adnan";

// if shirt number not taken, then insert in database
$statement = $pdo->prepare("INSERT INTO players (name, shirt_number) VALUES (:name, :code)");
$statement->execute([
    ":name" => $name,
    ":code" => $code
]);

// display the new shirt number
echo "<p>New code: " . $code . "</p>";

Complete code

<?php

// connect with database
$pdo = new PDO("mysql:host=localhost; dbname=test;", "root", "root", [
    PDO::ATTR_PERSISTENT => true
]);

// fetch all the players from the database
$statement = $pdo->prepare("SELECT * FROM players");
$statement->execute([]);
$rows = $statement->fetchAll(PDO::FETCH_OBJ);

// set number of range for shirt numbers
$number_range = 10;

// recursive function to return the new shirt number
function get_code()
{
    // define global variables to access them inside function
    global $rows, $number_range;

    // if all the numbers are taken, then return -1
    if (count($rows) >= $number_range)
        return -1;

    // generate a random number
    $code = rand(0, $number_range);

    // check if it already exists in the database
    foreach ($rows as $row)
    {
        if ($row->shirt_number == $code) // if number already exists, then start recursion i.e. generate another number
        return get_code();
    }

    // if number not already exists, then return it
    return $code;
}

// initial call to recursive function
$code = get_code();

// if all numbers are taken, display an error
if ($code == -1)
{
    echo "<p>All numbers are taken.</p>";
    exit;
}

// name of new player
$name = "Adnan";

// if shirt number not taken, then insert in database
$statement = $pdo->prepare("INSERT INTO players (name, shirt_number) VALUES (:name, :code)");
$statement->execute([
    ":name" => $name,
    ":code" => $code
]);

// display the new shirt number
echo "<p>New code: " . $code . "</p>";

Check if URL is valid – PHP

In order to check if a URL is valid in PHP, we are going to use the cURL. You can get the complete reference on cURL from php.net. First, we are going to create a variable that will hold the value of URL that needs to be checked.

<?php

// URL to check
$url = "https://adnan-tech.com/";

Then, we are going to initialize the cURL.

// Initialize cURL
$curl = curl_init();

After that, we need to set the URL of cURL and make sure to return the response from server. For this, we will use cURL options.

// Set cURL options
curl_setopt_array($curl, [
    // Set URL
    CURLOPT_URL => $url,

    // Make sure to return the response
    CURLOPT_RETURNTRANSFER => 1
]);

Finally, we can execute this cURL request.

// Execute the CURL request
$response = curl_exec($curl);

Then, we will get the error from response (if there is any). If there is no error in response, it means that the URL is valid and the curl_error will return an empty string.

// Get error from CURL, it will be empty if there is no error.
$error = curl_error($curl);

We can put a condition on this variable to check if this variable has some value, it means there is an error in the request. In that case, we will display the error and stop the script. Otherwise, we will display a message that the URL is valid.

// If not empty, display the error and stop the script.
if ($error)
    die($error);

// Else, the URL is valid
echo "Valid";

Here is the complete code.

<?php

// URL to check
$url = "https://adnan-tech.com/";

// Initialize CURL
$curl = curl_init();

// Set CURL options
curl_setopt_array($curl, [
    // Set URL
    CURLOPT_URL => $url,

    // Make sure to return the response
    CURLOPT_RETURNTRANSFER => 1
]);

// Execute the CURL request
$response = curl_exec($curl);

// Get error from CURL, it will be empty if there is no error.
$error = curl_error($curl);

// If not empty, display the error and stop the script.
if ($error)
    die($error);

// Else, the URL is valid
echo "Valid";

That’s how you can check if a URL is valid or not in PHP.

How to generate random string in PHP

In this article, I will show you, how you can generate a random string in PHP. You can set the length of random string. Also, you can define all the possible characters you want in your random string.

In order to generate random string in PHP, we will first create a string of all the possible characters we want in our random string.

$str = "qwertyuiopasdfghjklzxcvbnm";

Then we are going to get the length of that string.

$str_length = strlen($str);

After that, we will create a variable that will hold the output value i.e. random string.

$output = "";

Loop through the number of characters you want in your random string.

$length = 6;

for ($a = 1; $a <= $length; $a++)
{
    // [section-1]
}

On the place of [section-1], we will get the random index from string of possible characters.

$random = rand(0, $str_length - 1);

And we will pick the random character from that string.

$ch = $str[$random];

Finally, we will append that character in our output string.

$output .= $ch;

If you echo $output; you will see a random string every time that code is executed. Here is the full code:

$str = "qwertyuiopasdfghjklzxcvbnm";
$str_length = strlen($str);
$output = "";
$length = 6;
for ($a = 1; $a <= $length; $a++)
{
    $random = rand(0, $str_length - 1);
    $ch = $str[$random];
    $output .= $ch;
}
echo $output;

Capitalize string in PHP

In this blog post, you will learn how you can capitalize a string in PHP. We will not use any external library for this purpose. This code can be used in any PHP framework (Laravel, Yii) because I have not used any dependency for it. We will also teach you, how you can update the array element inside foreach loop in PHP.

First, we will explode the string by space.

<?php

// input string
$str = "adnan afzal";

// split words by space
$parts = explode(" ", $str); // ["adnan", "afzal"]
  • explode(delimiter, string): This is a PHP built-in function used to split the string into an array using a delimiter. Here, we are splitting the string using space. So it will return an array of all words in a string.

Then we will loop through all words one-by-one:

// loop through each word
foreach ($parts as $key => $value)
{
    // [section-1]
}

Inside the loop, we will update the word by uppercasing the first letter of each word. Write the following code in [section-1]:

// make first letter uppercase
// and update the word
$parts[$key] = ucfirst($value); // converts ["adnan"] to ["Adnan"]
  • ucfirst(string): This is another PHP built-in function and it is used to uppercase the first letter of the string. So “adnan” will became “Adnan”.

We are converting all the words first letter to uppercase and update the word itself. You can update the array value inside foreach loop using the $key variable.

After the loop, we will join all array parts by space.

// join all words by space
$str = implode(" ", $parts); // converts ["Adnan", "Afzal"] to "Adnan Afzal"

// output string
echo $str;
  • implode(glue, pieces): This function will join the array into a string using a glue. In this case, we are joining the array using space.

Complete code

Following is the complete code to capitalize the string in PHP:

<?php

// input string
$str = "adnan afzal";

// split words by space
$parts = explode(" ", $str); // ["adnan", "afzal"]

// loop through each word
foreach ($parts as $key => $value)
{
    // make first letter uppercase
    // and update the word
    $parts[$key] = ucfirst($value);
}

// join all words by space
$str = implode(" ", $parts);

// output string
echo $str;

Bonus

There might be some cases where you have a value in database like “processing_order” and you want it to display like “Processing Order”. In this case, you just have to add 1 line before using explode() function.

$str = "processing_order";
$str = str_replace("_", " ", $str); // processing order

Then you can apply the above code to capitalize the string. If you want to learn how to capitalize string in Javascript, follow this.

Encode decode JWT in PHP

JWT (Json Web Tokens) can be used for user authentication. They are token-based. First, you need to encode the token and send it to the client. Client will save it in his local storage or cookie. In order to decode the token, user must provide the token. Both encode and decode function on JWT wll be performed on server side in PHP.

Although I did JWT authentication with Python and Mongo DB and with Node JS and Mongo DB. But today we do it for PHP developers.

Install php-jwt

First, you need to install a library called “php-jwt”.

You can install it from composer:

COMPOSER_MEMORY_LIMIT=-1 composer require firebase/php-jwt

Or you can download and include it manually in your PHP project.

Encode JWT in PHP

When admin is logged-in, we need to generate his authentication token. First, you need to include the JWT and Key class on top of your file.

require_once "vendor/autoload.php";

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

Following code will generate a JWT using ID.

$jwt_key = "your_secret_key"; // This should be consistent

$issued_at = time();

// $expiry = strtotime("+30 days");

// jwt valid for 30 days (60 seconds * 60 minutes * 24 hours * 30 days)
$expiry = $issued_at + (60 * 60 * 24 * 30);

$payload = [
    'iss' => 'https://your-website.com',
    'aud' => 'https://your-website.com',
    'iat' => $issued_at, // issued at
    'nbf' => $issued_at, //  not before
    'exp' => $expiry, // expiry time
    "id" => 1
];

$jwt = JWT::encode($payload, $jwt_key, 'HS256');
  • Here, we have set the expiry date of this token to 30 days.
  • iss: Issuer. It tells who issued this token. Usually it is a URL of the server from where the JWT is being generated.
  • aud: Audience. This tells who can use this token. By providing the URL of your website, you are telling that this token will be valid if it comes from your website only. You can also pass it as an array if you want to allow multiple audience for same token.
  • iat: It will be a timestamp in seconds since January 01, 1970 00:00:00 UTC.
  • nbf: The timestamp seconds after which the token can be used. Token provided before this time will not be accepted. We are setting it same as issued time, so you will be able to use it as soon as you generate it.
  • exp: This will be the timestamp in seconds when the token will be expired. It will also be timestamps seconds since January 01, 1970 00:00:00 UTC. We are setting it’s validity for 30 days.
  • id: Optional. This is the custom claim that we are attaching with JWT. So when we decode the token, we will get this ID. This ID can be used to check if the user still exists in the database.

Here we are using HS256 algorithm that is used for authentication in JWT. It is a combination of 2 cryptographic methods:

  1. HMAC (Hash-based Message Authentication Code)
  2. SHA-256 (Secure Hash Algorithm 256 bit)

HMAC

HMAC combines cryptographic hash function with a secret key, in this case it will be $jwt_key. It ensures that the token is not been changed and is sent from the owner of secret key (your server).

SHA-256

SHA-256 generates a 256-bit hash from any given value. It is a one-way encryption method. It means that once the hash is generated, it cannot be decrypted back to its original state. So you will not be able to see the original message once it is encrypted.

So in JWT::encode function call, we are sending our payload and secret key. We are also telling the php-jwt library to use the HS256 algorithm. It takes our payload and encrypt it with our secret key and return the hash.

You do not have to save this token in your database. Just return it as a response to AJAX request and user can save it in his local storage.

localStorage.setItem("accessToken", accessToken)

To save the token on user side, we are using localStorage web API. This will keep the access token in local storage until removed using Javascript localStorage.removeItem(“accessToken”) or if the browsing history is deleted. However, it can store only upto 10 MB. But that will be enough in this case. The token generated will be a string of just a few bytes.

It stores the data in key-value pair. In this case, our key is “accessToken” and our value is the accessToken received from server.

Note: The first parameter is a string and second is a variable.

Decode JWT in PHP

Now whenever admin sends an AJAX request, he needs to pass that access token in headers.

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

ajax.setRequestHeader("Authorization", "Bearer " + localStorage.getItem("accessToken"))

ajax.send()

Here, we are first creating an AJAX (Asynchronous JavaScript and XML) object. Then we are opening the request with method POST and the server URL is “index.php”, you can write your own route. Third parameter is async, it’s default value is also true. It means that the request will be processed asyncronously. If you use false, it will not return any value until the response is received, thus blocking the UI which is not good for user experience.

After that, we are attaching Authorization header with the AJAX request. It is a good practice to send authorization tokens in header for security and it is also a standardized way of sending tokens. So if you are developing a website or a mobile application, every developer will know that the token needs to be sent in the header.

We are using Bearer authorization token because they hide the sensitive data that we are sending in payload. Thus, even if someone reads the headers, he won’t be able to read the ID of user. And we are fetching the token value from local storage we saved earlier.

Finally, we are sending the request. If you are to send some data with the request too, you can send it in FormData object. Check our tutorial on how to send AJAX request with FormData.

Then on server side, we need to get the access token from Authorization header and decode it to see if it is valid.

// index.php

// Get the JWT from the Authorization header
try
{
    $headers = getallheaders(); // returns an array of all headers attached in this request

    if (!isset($headers['Authorization']))
    {
        echo json_encode([
            "status" => "error",
            "message" => "'authorization' header not present."
        ]);
        
        exit();
    }

    $auth_header = $headers['Authorization'];
    list($jwt) = sscanf($auth_header, "Bearer %s");

    $decoded = JWT::decode($jwt, new Key($jwt_key, 'HS256'));
    $id = $decoded->id;

    return $id;
}
catch (\Exception $exp)
{
    echo json_encode([
        "status" => "error",
        "message" => $exp->getMessage()
    ]);

    exit();
}

First, we are fetching all the headers from the request. Then we are checking if the Authorization header is present in the request. If not, then we are throwing an error. So if user fails to attach the Authorization header in the request, it will not process further.

  • sscanf: This function is used to read and parse the value from a string. While “sprintf” is used to only format the data, but this function reads the data and parses as well.
  • Our format “Bearer %s” tells the function to expecct Bearer at the start of the string. And %s tells it to put the remaining string in a variable.
  • list() is a built-in function in PHP. It is used to assign multiple values to multiple variables respectively. In this case, we are assigning value of “%s” to $jwt variable.

After that, we are decoding the token. Decode function in JWT PHP is a little different than the encode function. First parameter is the token, second is an instance of Key class from php-jwt library. It also accepts 2 parameters, first is the secret key used to generate the token and second is the algorithm used while generating the token. If there is any mismatch in token, secret key or algorithm, it will throw an exception and we will receive that in catch block.

It will return the payload that we are saving in $decoded variable. From this decoded variable, you can get the ID of the authenticated user.

That’s how you can encode and decode the JWT in PHP. If you face any problem in following this, kindly do let me know.

Real Estate website in PHP, MySQL, Laravel

Real estate business that requires a website for their business, we are providing a free website for them in PHP and MySQL (Laravel framework).

Screenshots

Features

Following are the key features in this project. More features can be added on-demand.

Admin panel

There is a dedicated admin panel from where admin can manage all properties, set business email, phone and address. He can also manage the banners on home page. Administrator can also see all the messages received from “contact us” form from use side.

Properties

In this free real estate website, you can mention the properties available. From admin panel, you can add, edit or delete any property you want. You just need to enter the property name and its price. You can also upload an image of the property. For more information, you can write the detail on the “description” box.

Apart from these, you can also mention the following attributes of the property:

  • Area of property.
  • Number of floors.
  • Number of rooms.
  • Number of bedrooms.
  • Number of bathrooms.
  • Parking space.
  • And payment process (Cash, Bank, Cheque etc.).

Home banners

You can upload banners for home page. When user lands on your real estate website, he will see those banners right infront of him. You can upload as many banners as you want, but the ideal number is 3. You can delete any banner whenever you want.

Business information

To update the business information like phone, email and address. You don’t need to go into source code and update these on all places. On admin panel, you can goto “settings” and enter their values and they will be updated on all places in the website. You can also set the name of website from settings page.

Social network

Same as you can update your business information, you can enter your social media links from admin panel. Right now you can set the following social network links:

  • Facebook
  • Twitter
  • Instagram
  • LinkedIn

But more can be added on-demand.

Contact us

On user-side, we have a “contact us” form from where user can enter his name and email and send a message. You can see all user’s sent messages on admin panel. You can see their name and email, so if you want to contact them back, you can do easily.

Installation

  1. Create a database “real_estate” in your phpMyAdmin.
  2. Goto config/database.php.
  3. Set username and password for MySQL.

Run the following commands at root directory:

  1. COMPOSER_MEMORY_LIMIT=-1 composer update
  2. php artisan storage:link
  3. php artisan key:generate
  4. php artisan migrate
  5. name=”Admin” email=”admin@gmail.com” password=”admin” php artisan db:seed –class=DatabaseSeeder
  6. php artisan serve

Project will start running at: http://127.0.0.1:8000

Check if string is valid JSON array or object – PHP

Check if string is valid JSON array or object in PHP using PHP build-in functions. There are 2 ways to check the validity.

1st method (json_decode)

If you are getting a PHP variable who’s value can be either a JSON array or an JSON object, and you want to check if the JSON string is either an array or an object, then you can use PHP built-in json_decode function.

This function accepts the string as a parameter and return the decoded array or object. If it fails to decode the JSON string, it will return the null value.

2nd method (json_last_error)

Second method to check the validity of JSON string is to use the PHP built-in json_last_error function. This will return the error code of last decode attempt on JSON string. It returns a numerical value. 0 means there is no error. An enum JSON_ERROR_NONE can be used to compare the value. You can check all other possible values from PHP official documentation.

3rd method (json_validate, requires PHP >= 8.3)

However, there is a third method too but it requires PHP >= 8.3. It is the latest version of PHP at the time of writing this. So it is not upgraded in many of the websites, that is why you should use this only when you are sure that your site is running on PHP 8.3 or above. A new function json_validate is introduction in PHP 8.3. It accepts only 1 parameter, a JSON string, and return true or false if the string is a valid JSON or not.

Here is the complete code on how you can do that:

<?php

$json = "adnan-tech.com";

$json = '["Adnan"]';

$json = '{"name": "Adnan"}';

echo $json . " = ";

// if (json_validate($json))
// {

// }

$result = json_decode($json);

// if (json_last_error() === JSON_ERROR_NONE)

if ($result != null)
{
    echo "Valid <br />";

    if (is_array($result))
    {
        echo "Array";
    }

    if (is_object($result))
    {
        echo "Object";
    }
}
else
{
    echo "In-valid";
}

I have given 3 possible values of JSON string.

  1. It can be an in-valid string.
  2. It can be a valid array.
  3. It can be a valid object.

Also, I have written both 2 conditions that can be used to check the validity of JSON string.

That’s how you can check if the string is valid JSON array or an object in PHP. If you want to know how to encode the data from database into an array, check our tutorial.

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.