Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Verify Laravel Package

sanjabteam/verify

A Laravel/PHP package for mobile number verification via SMS. It generates and validates one-time codes with customizable length and expiry, manages resend limits, supports configurable SMS drivers, and offers a simple API for secure passwordless registration and login flows.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're adopting first. This package is archived. It will not receive Laravel-version bumps, security fixes, or PHP 8.x+ compatibility patches. Treat the install as "vendor and own it" rather than "add a dependency." Before you commit:

  1. Skim src/ in the repo — the whole surface is small (a service provider, a trait or two, a config file, possibly a migration and a contract for the SMS sender). Ten minutes of reading beats the README, which is sparse.
  2. Note the composer.json constraints on illuminate/support and php. On modern Laravel (10/11/12) you will likely need to fork and widen those constraints, or alias the package.
  3. Confirm whether it is on Packagist and at what version, versus requiring it as a VCS repository from GitHub.

Minimal install

composer require sanjabteam/verify
php artisan vendor:publish --provider="SanjabTeam\Verify\VerifyServiceProvider"
php artisan migrate

If the service provider is not auto-discovered (older packages sometimes omit it), register it manually in config/app.php under providers.

Wire it to your user model

use SanjabTeam\Verify\Traits\Verifiable; // confirm exact namespace in src/

class User extends Authenticatable
{
    use Verifiable;
}

First use case: mobile OTP login. The shortest path to value is a phone-number login form that (a) looks up or creates a user by mobile, (b) sends a code, (c) verifies it and logs the user in. Do that end-to-end before touching password reset or second-factor flows — it exercises config, the SMS sender, the code store, and expiry in one pass.

Where to look when something is wrong, in order: config/verify.php (code length, TTL, resend throttle, SMS driver), the trait, then the service provider's bindings. Most surprises live in config.

php artisan config:clear   # after editing config/verify.php

Implementation Patterns

Two distinct flows, one package. Decide up front whether you are doing verification of an existing attribute (user registered with a phone, now prove ownership) or authentication by phone (no password at all). The package stores codes against a model; the flow around it is yours. Keeping them separate avoids the classic bug of letting an unverified registration row log in.

Registration-then-verify

$user = User::create([...]);           // mobile_verified_at null
$user->sendVerifyCode();               // or the package's equivalent method
// later, in the verify controller:
if ($user->verifyCode($request->code)) {
    $user->markMobileAsVerified();
    Auth::login($user);
}

Login by OTP — don't create users silently on the login endpoint unless the product wants it. Look up by mobile, 404/redirect if absent, then send. This also prevents account enumeration if you return a generic message.

Always gate the "send" endpoint. OTP packages generate and store the code; they rarely rate-limit aggressively enough for production. Layer your own throttle:

Route::post('/otp/send', [OtpController::class, 'send'])
    ->middleware('throttle:3,1');    // 3 sends per minute per IP

Also throttle per-identifier (mobile number), not just per IP — an attacker rotating IPs against one number is the common case.

Custom SMS sender. The package almost certainly exposes a contract or a configurable driver for the actual send. Bind your provider (Kavenegar, Twilio, an internal gateway) in a service provider rather than editing the package:

$this->app->bind(SmsSender::class, KavenegarSender::class);

Keep the sender thin: it takes a mobile and a message and returns success/failure. Retry, logging, and provider fallback belong in a small wrapper, not in the package.

Queue the send. Sending SMS inside the request handler makes your login endpoint hostage to a third-party API. If the package's send is synchronous, wrap the call in a job:

SendOtp::dispatch($user)->onQueue('sms');

Be aware this changes the UX contract — the user may submit the code before the SMS lands. Show "code sent" optimistically and let the verify endpoint be the source of truth.

Use middleware for verified-only routes. Whichever way the package flags verification (mobile_verified_at, a boolean, a separate table), wrap it in a small middleware so your route files stay readable:

Route::middleware(['auth', 'verified.mobile'])->group(function () { ... });

API vs. web. If the package ships controllers or redirect responses, they were likely written for a session-based Blade app. For a JSON API, skip its controllers entirely and call the trait methods from your own thin controllers returning {"status": "ok"}. This is usually less work than trying to coerce redirects into JSON.

Testing. Don't hit the real SMS gateway in tests. Two options:

  • Fake the sender contract ($this->mock(SmsSender::class)) and assert it was called with the right number.
  • Read the code straight from the store (DB column or cache) in the test and post it to the verify endpoint — the most end-to-end assertion you can make.
$user->sendVerifyCode();
$code = DB::table('verify_codes')->where('user_id', $user->id)->value('code'); // adjust to actual schema
$this->post('/otp/verify', ['code' => $code])->assertRedirect('/dashboard');

Cleanup. Codes accumulate. Add a scheduled command (or a model prune) to delete expired rows nightly:

$schedule->command('model:prune')->daily();

Or a direct ->where('created_at', '<', now()->subHours(24))->delete() in a console command.

Gotchas and Tips

Archived means pinned. Composer will not resolve sanjabteam/verify against Laravel 11+ if the illuminate/* constraint says ^5.5|^6.0. Your options, in order of preference: fork it into your org and maintain it, copy the ~200 lines into app/Support/Otp and drop the dependency, or find a maintained alternative. Do not run composer update blindly once it's in — lock file is your friend.

The migration may collide. Many small OTP packages add columns to users (verify_code, mobile_verified_at) or create a verify_codes table. If you already have a mobile_verified_at, edit the published migration before running it, or you'll get duplicate-column errors that are trivial but annoying to unwind on a live DB.

Config cache bites. config/verify.php values are read at boot. In a cached-config environment (every production deploy), editing the file does nothing until php artisan config:cache is re-run. If your code length or TTL "isn't taking effect," this is why 90% of the time.

Short codes are brute-forceable. A 4-digit code and an unthrottled verify endpoint is a 10,000-request problem. Enforce: code length ≥ 5 (ideally 6), a max-attempts counter per code (invalidate after 3–5 failures), and a global verify throttle. Check whether the package increments an attempt counter or just compares strings — if it doesn't, add the counter yourself in your controller.

Expiry and timezones. Compare created_at/expires_at in the same timezone. Laravel stores UTC; if the package does now()->diffInMinutes($created_at) against a stored date('Y-m-d H:i:s') from a non-UTC PHP default, codes expire early or late. Set APP_TIMEZONE=UTC and confirm the package uses Carbon consistently, not time().

Race conditions on verify. Two requests with the same valid code can both succeed if the check is "read then delete" outside a transaction. Wrap verification in DB::transaction with lockForUpdate, or make the "consume code" an atomic UPDATE ... WHERE code = ? AND used_at IS NULL and check the affected-rows count.

Re-send invalidates the old code? Confirm the behaviour. Some packages overwrite the code on resend, some keep multiple. Overwriting is safer (only one valid code at a time); if the package doesn't, delete prior codes in your send path.

Log the code in local only. For developer sanity you want to see the OTP without an SMS gateway. Add it in AppServiceProvider via an event listener guarded by app()->isLocal(), not by editing the package — you'll forget you edited it.

if (app()->isLocal()) {
    Log::info('OTP', ['mobile' => $mobile, 'code' => $code]);
}

Enumeration via timing. A verify endpoint that returns different errors ("no such user" vs. "wrong code") leaks which numbers are registered. Return a uniform 422 Invalid code for all failure modes.

Extension points to prefer over forking. In rough order of safety: your own controller around the trait → a custom SMS sender binding → a subclass with overridden methods → a fork. Only fork for PHP/Laravel compatibility, not for behaviour changes, so upstream diffs (if any ever come) stay mergeable.

If you fork, document why. Put a FORK_NOTES.md at the root listing every divergence. Six months from now the next developer will assume the GitHub repo is authoritative and "fix" your changes away.

When not to use it. If you need WhatsApp/voice fallback, multi-channel OTP, or TOTP as a second factor, this package's surface is too small — you'll spend more time bending it than writing a 150-line OTP service. For a single SMS OTP on a Laravel 5/6-era app it's a reasonable start; on a modern stack, the archived status alone usually tips the balance toward a maintained alternative.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules