Search by

trianity / laravel-otp

trianity

OTP Generator and Validator for Laravel Applications

Package info

github.com/trianity/laravel-otp

pkg:composer/trianity/laravel-otp

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-10-03 14:33 UTC

This package is auto-updated.

Last update: 2026-10-03 14:37:17 UTC


README

Source code · Changelog · MIT License

OTP generator and validator for Laravel applications. The package stores OTP records in the database and supports configurable token length, expiry, regeneration limits, and validation attempt limits.

The package was inspired by seshac/otp-generator.

Generated OTP values are returned only from generate(). The database stores a hash of the token, and a successfully validated OTP is immediately marked as expired so it cannot be reused.

Requirements for 1.0.0

  • PHP ^8.4 (including PHP 8.4 and 8.5)
  • Laravel 12 or Laravel 13 (illuminate/support ^12.0|^13.0)

Laravel 13 is the primary target. A database connection configured in your Laravel application is required to store OTP records.

Installation

Once version 1.0.0 is published on Packagist, install the stable 1.x series with Composer:

composer require trianity/laravel-otp:^1.0

Laravel automatically discovers the service provider and the Otp facade; manual registration is not required.

The package automatically loads its migrations. Run your migrations after installation:

php artisan migrate

Publish the configuration and translation files when you want to customize them:

php artisan vendor:publish --provider="Trianity\Otp\Providers\PackageServiceProvider" --tag="otp"

This publishes the following files:

  • config/otp.php
  • lang/vendor/otp

Basic Usage

use Illuminate\Support\Str;
use Trianity\Otp\Facades\Otp;

$identifier = Str::random(12);

$otp = Otp::generate($identifier);

if ($otp->status && $otp->token !== null) {
    // Deliver this token through your application, then validate the submitted code.
    $verify = Otp::validate($identifier, $otp->token);
}

On success, generate() returns an object with status, token, message, and code. On failure, it returns status => false, message, and code; check status before accessing token. The plain-text token is available in the response only; it is never stored in the database.

Successful validation returns an object similar to:

(object) [
    'status' => true,
    'message' => 'OTP is valid',
    'code' => 0,
]

After a successful validation, validating the same OTP again returns a failed response because the stored record is marked as expired.

Get the expiration time for an existing OTP:

$expires = Otp::expiredAt($identifier);

The returned object contains an expired_at Carbon instance when the OTP exists.

If an OTP does not exist, has expired, or has reached its attempt limit, validate() returns status => false. A successful validation consumes the OTP.

Validation Rule

Use Trianity\Otp\Rules\OtpRule when the OTP check belongs in a Laravel validator or FormRequest.

use Trianity\Otp\Rules\OtpRule;

$request->validate([
    'otp' => ['required', 'string', new OtpRule($identifier)],
]);

The rule calls Otp::validate(). A successful validation consumes the OTP, so use it only in the final step of the login or verification flow.

Controller Example

The package does not register application routes. Keep routing, guards, user lookup, session handling, and responses in your app:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Trianity\Otp\Rules\OtpRule;

class OtpLoginController extends Controller
{
    public function verify(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'email' => ['required', 'email'],
            'otp' => ['required', 'string', new OtpRule($request->string('email')->toString())],
        ]);

        $user = User::where('email', $data['email'])->firstOrFail();

        Auth::login($user);
        $request->session()->regenerate();

        return redirect()->intended('/dashboard');
    }
}

Configuration

You can configure the package in config/otp.php:

return [
    'validity' => env('OTP_VALIDITY_TIME', 30),
    'length' => env('OPT_LENGTH', 6),
    'allowedAttempts' => env('OTP_ALLOWED_ATTEMPTS', 5),
    'onlyDigits' => true,
    'useSameToken' => false,
    'deleteOldOtps' => 31,
    'maximumOtpsAllowed' => env('MAXIMUM_OTPS_ALLOWED', 5),
];

The default configuration allows five validation attempts and five generated OTPs per identifier during the cleanup period. The OPT_LENGTH variable name is retained for compatibility with the package configuration.

Advanced Usage

Configuration values can also be overridden fluently for a call chain:

use Illuminate\Support\Str;
use Trianity\Otp\Facades\Otp;

$identifier = Str::random(12);

$otp = Otp::setValidity(30)
    ->setLength(4)
    ->setMaximumOtpsAllowed(10)
    ->setOnlyDigits(false)
    ->setUseSameToken(true)
    ->generate($identifier);

$verify = Otp::setAllowedAttempts(10)
    ->validate($identifier, $otp->token);

Fluent overrides apply to the current terminal call only. After generate(), validate(), or expiredAt(), the generator resets to config/otp.php values.

Available fluent setters map to the package settings:

  • setValidity(int $minutes)
  • setLength(int $length)
  • setMaximumOtpsAllowed(int $count)
  • setOnlyDigits(bool $onlyDigits)
  • setUseSameToken(bool $useSameToken)
  • setAllowedAttempts(int $count)

Because tokens are stored as hashes, setUseSameToken(true) keeps the existing stored token valid for the identifier, but it returns token => null on later calls. If you need to resend the same code, keep the generated token from the original generate() response in your delivery flow.

Database and cleanup

The package loads its OTP migration automatically. Each identifier has one stored OTP record; regenerating an OTP updates that record. Expired records and records older than deleteOldOtps minutes are removed when a new OTP is generated.

The package does not register routes, send messages, or implement a login flow. Deliver the returned token through the channel used by your application and keep routing, user lookup, guards, sessions, and responses in your app.

Testing

From a source checkout, install development dependencies and run the checks:

composer install
./vendor/bin/pest
./vendor/bin/phpstan analyse

Changelog

See CHANGELOG.md for the 1.0.0 release notes and subsequent changes.

License

This package is licensed under the MIT License.