Chat Widget
Chat with us!

Text.lk SMS Gateway Sri Lanka | Cheapest price on the market (0.64LKR per SMS)

  +94 77 644 00 80

Login         Register

How to Implement OTP (One-Time Password) Verification Using Laravel, MySQL & Text.lk SMS Gateway

Below is a full, clean tutorial specifically for Laravel + MySQL + Text.lk, using the official Text.lk Laravel package.

OTP (One-Time Password) verification is a critical feature for modern web applications. It is widely used for:

  • User registration
  • Login without passwords
  • Password reset
  • Sensitive actions (payments, profile changes)

In this tutorial, you’ll learn how to build a secure OTP verification system using:

  • Laravel
  • MySQL
  • Text.lk SMS Gateway (Laravel Package)

We’ll use the official package:
👉 https://laravelpackages.net/textlk/textlk-laravel


OTP Verification Flow

  1. User enters mobile number
  2. System generates a 6-digit OTP
  3. OTP is saved in database (hashed)
  4. OTP is sent via Text.lk SMS
  5. User submits OTP
  6. OTP is verified and marked as used

Step 1: Install Text.lk Laravel Package

Run the following command:

composer require textlk/textlk-laravel

Laravel will auto-discover the package.


Step 2: Configure Environment Variables

Add your Text.lk credentials to .env:

TEXTLK_API_KEY=your_textlk_api_key
TEXTLK_SENDER_ID=TEXTLK
OTP_EXPIRY_MINUTES=5

Step 3: Create OTP Database Table

Create migration:

php artisan make:migration create_otp_verifications_table

Migration file:

public function up()
{
    Schema::create('otp_verifications', function (Blueprint $table) {
        $table->id();
        $table->string('mobile', 15);
        $table->string('otp_hash');
        $table->timestamp('expires_at');
        $table->boolean('is_verified')->default(false);
        $table->timestamps();
    });
}

Run migration:

php artisan migrate

Step 4: Create OTP Model

php artisan make:model OtpVerification
class OtpVerification extends Model
{
    protected $fillable = [
        'mobile',
        'otp_hash',
        'expires_at',
        'is_verified'
    ];

    protected $casts = [
        'expires_at' => 'datetime'
    ];
}

Step 5: Create OTP Controller

php artisan make:controller OtpController

Step 6: Generate & Send OTP Using Text.lk

use App\Models\OtpVerification;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use TextLK\SMS;
use Carbon\Carbon;

class OtpController extends Controller
{
    public function sendOtp(Request $request)
    {
        $request->validate([
            'mobile' => 'required|string'
        ]);

        $otp = rand(100000, 999999);

        OtpVerification::create([
            'mobile' => $request->mobile,
            'otp_hash' => Hash::make($otp),
            'expires_at' => Carbon::now()->addMinutes(env('OTP_EXPIRY_MINUTES', 5))
        ]);

        $sms = new SMS(env('TEXTLK_API_KEY'));

        $sms->send([
            "recipient" => $request->mobile,
            "sender_id" => env('TEXTLK_SENDER_ID', 'TEXTLK'),
            "type" => "plain",
            "message" => "Your OTP code is {$otp}. Valid for 5 minutes."
        ]);

        return response()->json([
            'status' => 'success',
            'message' => 'OTP sent successfully'
        ]);
    }

Step 7: Verify OTP

    public function verifyOtp(Request $request)
    {
        $request->validate([
            'mobile' => 'required|string',
            'otp' => 'required|string'
        ]);

        $otpRecord = OtpVerification::where('mobile', $request->mobile)
            ->where('is_verified', false)
            ->latest()
            ->first();

        if (!$otpRecord) {
            return response()->json([
                'status' => 'error',
                'message' => 'OTP not found'
            ]);
        }

        if ($otpRecord->expires_at->isPast()) {
            return response()->json([
                'status' => 'error',
                'message' => 'OTP expired'
            ]);
        }

        if (!Hash::check($request->otp, $otpRecord->otp_hash)) {
            return response()->json([
                'status' => 'error',
                'message' => 'Invalid OTP'
            ]);
        }

        $otpRecord->update(['is_verified' => true]);

        return response()->json([
            'status' => 'success',
            'message' => 'OTP verified successfully'
        ]);
    }
}

Step 8: Define Routes

use App\Http\Controllers\OtpController;

Route::post('/otp/send', [OtpController::class, 'sendOtp']);
Route::post('/otp/verify', [OtpController::class, 'verifyOtp']);

Step 9: Simple Frontend Example

Request OTP

<form method="POST" action="/otp/send">
    @csrf
    <input type="text" name="mobile" placeholder="07XXXXXXXX" required>
    <button type="submit">Send OTP</button>
</form>

Verify OTP

<form method="POST" action="/otp/verify">
    @csrf
    <input type="text" name="mobile" required>
    <input type="text" name="otp" placeholder="Enter OTP" required>
    <button type="submit">Verify OTP</button>
</form>

Security Best Practices

✔ Always hash OTPs
✔ Short expiry time (3–5 minutes)
✔ Limit OTP requests per mobile number
✔ Prevent brute-force attempts
✔ Use HTTPS
✔ Delete old OTP records with cron


Optional: Auto Cleanup Old OTPs

OtpVerification::where('expires_at', '<', now())->delete();

Run via scheduled task.


Common Use Cases

  • Login without password
  • User registration verification
  • Password reset
  • Payment confirmation
  • Admin approval flows

➕ Extra OTP Features for Laravel + Text.lk

This section covers advanced OTP features commonly used in modern SaaS and API-based applications.


1. OTP-Only Login (Passwordless Authentication)

OTP-only login allows users to log in without a password, using only their mobile number and OTP.

Flow

  1. User enters mobile number
  2. OTP is sent
  3. OTP is verified
  4. User is logged in automatically

Step 1: Ensure User Table Has Mobile Column

$table->string('mobile')->unique();

Step 2: OTP Login Controller Method

use App\Models\User;
use Illuminate\Support\Facades\Auth;

public function otpLogin(Request $request)
{
    $request->validate([
        'mobile' => 'required',
        'otp' => 'required'
    ]);

    $otpRecord = OtpVerification::where('mobile', $request->mobile)
        ->where('is_verified', false)
        ->latest()
        ->first();

    if (!$otpRecord || $otpRecord->expires_at->isPast()) {
        return response()->json(['message' => 'OTP invalid or expired'], 401);
    }

    if (!Hash::check($request->otp, $otpRecord->otp_hash)) {
        return response()->json(['message' => 'Invalid OTP'], 401);
    }

    $user = User::firstOrCreate(
        ['mobile' => $request->mobile],
        ['name' => 'User '.$request->mobile]
    );

    $otpRecord->update(['is_verified' => true]);

    Auth::login($user);

    return response()->json([
        'status' => 'success',
        'message' => 'Logged in successfully'
    ]);
}

Route

Route::post('/otp/login', [OtpController::class, 'otpLogin']);

2. API OTP Login with Laravel Sanctum

This is essential for mobile apps, SPA, and SaaS APIs.


Step 1: Install Sanctum

composer require laravel/sanctum
php artisan sanctum:install
php artisan migrate

Add middleware in api group (Laravel 10+ usually auto-enabled).


Step 2: API OTP Login with Token

public function apiOtpLogin(Request $request)
{
    $request->validate([
        'mobile' => 'required',
        'otp' => 'required'
    ]);

    $otp = OtpVerification::where('mobile', $request->mobile)
        ->where('is_verified', false)
        ->latest()
        ->first();

    if (!$otp || $otp->expires_at->isPast()) {
        return response()->json(['message' => 'OTP expired'], 401);
    }

    if (!Hash::check($request->otp, $otp->otp_hash)) {
        return response()->json(['message' => 'Invalid OTP'], 401);
    }

    $user = User::firstOrCreate(
        ['mobile' => $request->mobile],
        ['name' => 'User '.$request->mobile]
    );

    $otp->update(['is_verified' => true]);

    $token = $user->createToken('otp-login')->plainTextToken;

    return response()->json([
        'token' => $token,
        'token_type' => 'Bearer'
    ]);
}

API Routes

Route::post('/api/otp/login', [OtpController::class, 'apiOtpLogin']);

Protect API Routes

Route::middleware('auth:sanctum')->get('/profile', function (Request $request) {
    return $request->user();
});

3. Resend OTP Logic (With Cooldown)

Prevent abuse by allowing resend only after X seconds.


$table->timestamp('last_sent_at')->nullable();

Resend OTP Controller Method

public function resendOtp(Request $request)
{
    $request->validate([
        'mobile' => 'required'
    ]);

    $lastOtp = OtpVerification::where('mobile', $request->mobile)
        ->latest()
        ->first();

    if ($lastOtp && $lastOtp->created_at->diffInSeconds(now()) < 60) {
        return response()->json([
            'message' => 'Please wait before requesting another OTP'
        ], 429);
    }

    $otp = rand(100000, 999999);

    OtpVerification::create([
        'mobile' => $request->mobile,
        'otp_hash' => Hash::make($otp),
        'expires_at' => now()->addMinutes(5)
    ]);

    $sms = new \TextLK\SMS(env('TEXTLK_API_KEY'));

    $sms->send([
        "recipient" => $request->mobile,
        "sender_id" => env('TEXTLK_SENDER_ID'),
        "type" => "plain",
        "message" => "Your new OTP is {$otp}. Valid for 5 minutes."
    ]);

    return response()->json([
        'status' => 'success',
        'message' => 'OTP resent successfully'
    ]);
}

Route

Route::post('/otp/resend', [OtpController::class, 'resendOtp']);

✔ Limit OTP attempts (max 3 tries)
✔ Block mobile temporarily after failures
✔ Use Laravel Rate Limiting
✔ Log OTP requests per IP
✔ Expire all previous OTPs on resend


Ideal Use Cases

  • Mobile app authentication
  • SaaS dashboards
  • Fintech & payment confirmation
  • Admin & staff verification
  • RapidAPI OTP services

Conclusion

You now have a fully functional OTP verification system using Laravel, MySQL, and Text.lk SMS, powered by the official Text.lk Laravel package.

This setup is:

  • Secure
  • Scalable
  • Ready for SaaS & enterprise apps
  • Perfect for Sri Lanka–based platforms

With extra features, your OTP system is now:

  • Passwordless
  • Scalable

Leave a Reply

Your email address will not be published. Required fields are marked *