Creating Your Own OTP Service Laravel (API)

Creating Your Own OTP Service Laravel (API)

Some weeks back, I needed to use otp for verificarion and validation in a project, instead of email and phone number, which has always been the default method for user verification. I found a way around it and I'd love to show how it's done.

Let's dive right into it… Create a new project using the command

Create a new project

laravel new otpservice
cd otpservice
php artisan serve

This creates a new laravel project called otpservice.

There are couple of files to create, a model, migration and controller that we will use throughout the project.

Create OTP model, migration and controller

php artisan make:model Otp -mc

Note: the command creates three files, a model, migration and controller.

The Otp migration file

Open up the newly generated migration file and add the following column to the up function

$table->id();
$table->foreignId('user_id')->unique()->nullable();
$table->string('otp');
$table->boolean('is_used')->default(false);
$table->timestamp('expires_at')->default(now()->addMinutes(10));
$table->timestamps();

Note: You can add as many as possible foreign ids you want to use to reference the otp table and make them unique and nullable. Here we will be using it for user verification thereby using the 'user_id'. Also the expires_at set a default of 10 minutes, you can make it whatever time you wish.

Run the migrate command

php artisan migrate

The Otp.php model file

Add the following fillable, casts, hidden properties to the model file

protected $fillable = [
'otp',
'is_used',
'expires_at'
];
protected $casts = [
    'expires_at' => 'datetime',
    'is_used' => 'boolean'
];
protected $hidden = [
    'otp'
];

In the Otp.php model class, add a one to one relationship which relates to the users table

public function user() {
    return $this->belongsTo(User::class);
}

Also, in the User.php model class, add the corresponding hasOne relationship.

public function otp() {
    return $this->hasOne(Otp::class);
}

The OtpController.php Controller File

Here is where we create all the methods needed to use the otp service, let's dive right into it.

Two methods are essential here, the generateOtp, regenerateOtp and verifyOtp.

<?php

namespace App\Http\Controllers;

use App\Models\Otp;
use Illuminate\Http\Request;

class OtpController extends Controller
{
    public function generateOtp(Request $request)
    {
        $user = $request->user();
        $otp = rand(1000, 9999);
        $userOtp = $user->otp;

        if ($userOtp) {
            $userOtp->update([
                'user_id' => $user->id,
                'is_used' => false,
                'otp' => $otp,
                'expires_at' => now()->addMinutes(10)
            ]);
        } else {
            Otp::create([
                'user_id' => $user->id,
                'is_used' => false,
                'otp' => $otp,
                'expires_at' => now()->addMinutes(10)
            ]);
        }

        return response()->json([
            'status' => true,
            'message' => "Otp code created"
        ], 201);
    }


    public function verifyOtp(Request $request, $otp)
    {
        $user = $request->user();

        $userOtp = $user->otp;

        if (!$userOtp || $userOtp->is_used || $userOtp->expires_at < now()) {
            return response()->json([
                'status' => false,
                'message' => "An error occurred",
                'error' => "Invalid OTP, kindly request for another one"
            ], 400);
        }

        if ($userOtp->otp == $otp) {

            // perform whatever operation you want to do when otp is verified

            $userOtp->update([
                'is_used' => true
            ]);

            return response()->json([
                'status' => true,
                'message' => "Otp verified"
            ], 200);
        }
        return response()->json([
            'status' => false,
            'message' => "An error occurred",
            'error' => "Otp is incorrect, please try again"
        ], 400);
    }
}

Note: You can used the generateOtp method to regenerateOtp incase otp expires or otp has been used

API routes

Route::middleware('auth:api')->group(function () {
    Route::get('/otp/generate', [OtpController::class, 'generateOtp']);
    Route::get('/otp/verify/{otp}', [OtpController::class, 'verifyOtp']);
});

Note: The auth:api middleware is used to get authenticated user

I will create a package for this and create a blog on how to use this soon!.

Link to the sample project

Follow me on Twitter

Follow me on LinkedIn

Conclusion

This is it!!!

If you have any questions or need to make a clarification, please drop a comment.