PHP code example of thecyrilcril / laravel-otp

1. Go to this page and download the library: Download thecyrilcril/laravel-otp library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

thecyrilcril / laravel-otp example snippets


use Thecyrilcril\Otp\Contracts\OtpPurpose;

enum Purpose: string implements OtpPurpose
{
    case EmailVerification = 'email_verification';
    case PhoneVerification = 'phone_verification';

    public function value(): string
    {
        return $this->value;
    }
}

use Thecyrilcril\Otp\Concerns\HasOtps;

final class User extends Authenticatable
{
    use HasOtps;
}

$issued = $user->issueOtp(Purpose::EmailVerification);

$issued->code;      // the plaintext, available exactly once
$issued->expiresAt; // CarbonImmutable

$user->verifyOtp(Purpose::EmailVerification, $code);   // check only, code survives
$user->consumeOtp(Purpose::EmailVerification, $code);  // check + delete, single-use

$user->issueOtp(Purpose::PhoneVerification, context: '+2348012345678');
$user->consumeOtp(Purpose::PhoneVerification, $code, context: $submittedPhone);

$user->issueOtp(Purpose::TwoFactorLogin);

use Thecyrilcril\Otp\Exceptions\OtpThrottledException;

try {
    $user->issueOtp(Purpose::EmailVerification);
} catch (OtpThrottledException $e) {
    // $e->retryAfterSeconds — seconds until the limit resets
}



declare(strict_types=1);

namespace App\Notifications;

use Carbon\CarbonImmutable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use SensitiveParameter;

final class SendOtpCode extends Notification implements ShouldBeEncrypted, ShouldQueue
{
    use Queueable;

    public function __construct(
        #[SensitiveParameter]
        private readonly string $code,
        private readonly CarbonImmutable $expiresAt,
    ) {}

    /** @return list<string> */
    public function via(mixed $notifiable): array
    {
        return ['mail'];
    }

    public function toMail(mixed $notifiable): MailMessage
    {
        $minutes = (int) max(1, round(now()->diffInMinutes($this->expiresAt)));

        return new MailMessage()
            ->subject('Your verification code')
            ->line('Use the code below to verify your account.')
            ->line("**{$this->code}**")
            ->line("This code expires in {$minutes} minute(s).")
            ->line("If you didn't request this, you can safely ignore this email.");
    }
}

$issued = $user->issueOtp(Purpose::EmailVerification);

$user->notify(new SendOtpCode($issued->code, $issued->expiresAt));

// ❌ the rollback erases the failed-guess record
DB::transaction(function () use ($user, $code) {
    if (! $user->consumeOtp(Purpose::EmailVerification, $code)) {
        throw ValidationException::withMessages([...]);   // attempts increment lost
    }
    $user->markEmailAsVerified();
});

// ✅ check outside; only the success path is transactional
$consumed = $user->consumeOtp(Purpose::EmailVerification, $code);

if (! $consumed) {
    throw ValidationException::withMessages([...]);
}

DB::transaction(fn () => $user->markEmailAsVerified());

'length'        => 6,   // digits; must be between 6 and 10 inclusive
'expires_after' => 10,  // minutes
'verify_limit'  => ['attempts' => 5, 'decay' => 60],    // per model+purpose
'issue_limit'   => ['attempts' => 3, 'decay' => 600],   // per model+purpose
'max_attempts'  => 5,   // per-code failure budget
'table'         => 'otps',

$schedule->command('model:prune', ['--model' => [\Thecyrilcril\Otp\Models\Otp::class]])->daily();
bash
composer vendor:publish --tag=otp-migrations
php artisan vendor:publish --tag=otp-config   # optional
php artisan migrate