PHPackages                             bbim/otpify - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. bbim/otpify

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

bbim/otpify
===========

Laravel OTP verification library with multiple drivers

04PHP

Since Sep 30Pushed 7mo agoCompare

[ Source](https://github.com/Ahmedd-Ibrahim/OTPIFY-PACKAGE)[ Packagist](https://packagist.org/packages/bbim/otpify)[ RSS](/packages/bbim-otpify/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

🔐 Otpify - Laravel OTP Verification Package
===========================================

[](#-otpify---laravel-otp-verification-package)

A comprehensive Laravel package for OTP (One-Time Password) verification with multiple drivers support, rate limiting, events system, and extensibility features.

✨ Features
----------

[](#-features)

- 🚀 **Multiple Drivers**: SMS (Twilio), Email, Absher, Authorization, and custom drivers
- 🔒 **Secure**: Hash-based OTP storage and validation
- ⏰ **Configurable**: Expiration times, code length, and rate limiting
- 📱 **Easy Integration**: Simple facade and trait-based implementation
- 🧪 **Testing Ready**: Fake drivers for development and testing
- 🌐 **Multi-language**: Built-in translation support
- 🎯 **Events System**: Hook into OTP lifecycle events
- 🛡️ **Rate Limiting**: Prevent abuse and brute force attacks
- 🔧 **Extensible**: Custom drivers and configurable models
- 📊 **Monitoring**: Comprehensive event logging

📦 Installation
--------------

[](#-installation)

```
composer require bbim/otpify
```

Publish configuration:

```
php artisan vendor:publish --tag=config
```

Run migrations:

```
php artisan migrate
```

⚙️ Configuration
----------------

[](#️-configuration)

### Environment Variables

[](#environment-variables)

Add these to your `.env` file:

```
# OTP Configuration
OTPIFY_DRIVER=email
OTPIFY_CODE_LENGTH=6
OTPIFY_EXPIRATION_MINUTES=3
OTPIFY_RATE_LIMITING_ENABLED=true
OTPIFY_MAX_SENDS=3
OTPIFY_RATE_LIMIT_DECAY=10
OTPIFY_MAX_VERIFY_ATTEMPTS=5
OTPIFY_LOCKOUT_MINUTES=30

# Twilio Configuration (for SMS driver)
OTPIFY_TWILIO_SID=your_twilio_sid
OTPIFY_TWILIO_TOKEN=your_twilio_token
OTPIFY_TWILIO_FROM=+1234567890

# Absher Configuration
ABSHER_API_KEY=your_absher_api_key
ABSHER_HOST=https://api.absher.sa

# Events Configuration
OTPIFY_EVENTS_ENABLED=true
OTPIFY_EVENTS_PRESET=all_enabled
OTPIFY_EVENT_OTP_SENDING=true
OTPIFY_EVENT_OTP_SENT=true
OTPIFY_EVENT_OTP_VERIFYING=true
OTPIFY_EVENT_OTP_VERIFIED=true
OTPIFY_EVENT_OTP_VERIFICATION_FAILED=true
```

### Model Setup

[](#model-setup)

Implement the `Otpifiable` contract in your User model:

```
use Bbim\Otpify\Contracts\Otpifiable;
use Bbim\Otpify\Traits\CanOtpifyCode;

class User extends Authenticatable implements Otpifiable
{
    use CanOtpifyCode;

    public function doesRequireVerifyingByOtp(Request $request): bool
    {
        // Your logic to determine if OTP is required
        return !$this->hasVerifiedEmail();
    }

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

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

    public function getMorphClass()
    {
        return static::class;
    }

    public function getKey()
    {
        return $this->getKey();
    }
}
```

🚀 Usage
-------

[](#-usage)

### Basic Usage

[](#basic-usage)

```
use Bbim\Otpify\Facades\Otpify;

// Send OTP
$otpCode = Otpify::driver('email')->send($request, $user);

// Verify OTP
$isValid = Otpify::driver('email')->verify($request, $otpCode->id, '123456');
```

### Using Different Drivers

[](#using-different-drivers)

```
// Email driver
$otpCode = Otpify::driver('email')->send($request, $user);

// SMS driver (Twilio)
$otpCode = Otpify::driver('twilio')->send($request, $user);

// Absher driver
$otpCode = Otpify::driver('absher')->send($request, $user);

// Fake driver (for testing)
$otpCode = Otpify::driver('fake_absher')->send($request, $user);
```

### With Additional Verification

[](#with-additional-verification)

```
$isValid = Otpify::driver('email')->verify($request, $otpCode->id, '123456', function($request, $otpifyCode) {
    // Additional verification logic
    return $request->ip() === $otpifyCode->data['ip_address'];
});
```

🎯 Custom Drivers
----------------

[](#-custom-drivers)

Create your own OTP driver:

### Step 1: Create Driver Class

[](#step-1-create-driver-class)

```
namespace App\Otpify\Drivers;

use Bbim\Otpify\Contracts\OtpifyDriverInterface;
use Bbim\Otpify\Contracts\Otpifiable;
use Bbim\Otpify\Models\OtpifyCode;
use Bbim\Otpify\Traits\CanOtpifyCode;
use Bbim\Otpify\Traits\RateLimitsOtp;
use Illuminate\Http\Request;

class WhatsAppDriver implements OtpifyDriverInterface
{
    use CanOtpifyCode, RateLimitsOtp;

    public function send(Request $request, Otpifiable $otpifiable, array $data = []): OtpifyCode
    {
        // Check rate limiting
        if ($this->hasTooManySendAttempts($otpifiable)) {
            throw new \Bbim\Otpify\Exceptions\TooManyOtpAttemptsException(
                $this->availableInForSend($otpifiable)
            );
        }

        $code = generateRandomCode(config('otpify.code_length'));

        // Your WhatsApp implementation
        $this->sendWhatsAppMessage($otpifiable->getPhoneNumber(), $code);

        $otpifyCode = $this->createOtpifyCode($code, $otpifiable, auth()->user(), $data);

        // Increment attempts
        $this->incrementSendAttempts($otpifiable);

        return $otpifyCode;
    }

    public function doesRequireVerifyingByOtp(Request $request, Otpifiable $otpifiable): bool
    {
        return $otpifiable->doesRequireVerifyingByOtp($request);
    }

    public function verify(Request $request, $vid, $code, ?\Closure $additionalCheckCallback = null): string|bool
    {
        // Check rate limiting
        if ($this->hasTooManyVerifyAttempts($vid)) {
            throw new \Bbim\Otpify\Exceptions\TooManyOtpAttemptsException(
                $this->availableInForVerify($vid)
            );
        }

        try {
            $otpifyCode = $this->getOtpifyCode($vid);
            $this->verifyOtpifyCode($otpifyCode, $request, $code, $additionalCheckCallback);
            $this->setOtpExpiredAt($otpifyCode);

            // Clear rate limiting on success
            $this->clearVerifyAttempts($vid);

            return true;
        } catch (\Throwable $e) {
            // Increment failed attempts
            $this->incrementVerifyAttempts($vid);
            throw $e;
        }
    }

    private function sendWhatsAppMessage(string $phone, string $code): void
    {
        // Your WhatsApp API implementation
    }
}
```

### Step 2: Register in Config

[](#step-2-register-in-config)

```
// config/otpify.php

'custom_drivers' => [
    'whatsapp' => \App\Otpify\Drivers\WhatsAppDriver::class,
],
```

### Step 3: Use It

[](#step-3-use-it)

```
use Bbim\Otpify\Facades\Otpify;

$otpCode = Otpify::driver('whatsapp')->send($request, $user);
```

🎉 Events
--------

[](#-events)

### Event Configuration

[](#event-configuration)

#### Global Control

[](#global-control)

```
# Master switch for all events
OTPIFY_EVENTS_ENABLED=true

# Set preset (all_enabled, all_disabled, critical_only, debug_mode)
OTPIFY_EVENTS_PRESET=all_enabled
```

#### Individual Event Control

[](#individual-event-control)

```
# Control each event separately
OTPIFY_EVENT_OTP_SENDING=true
OTPIFY_EVENT_OTP_SENT=true
OTPIFY_EVENT_OTP_VERIFYING=true
OTPIFY_EVENT_OTP_VERIFIED=true
OTPIFY_EVENT_OTP_VERIFICATION_FAILED=true
```

#### Configuration File

[](#configuration-file)

```
// config/otpify.php
'events' => [
    'enabled' => true,                    // Master switch
    'preset' => 'all_enabled',           // Current preset
    'individual' => [                    // Override specific events
        'otp_sending' => true,
        'otp_sent' => true,
        // ...
    ],
    'presets' => [                       // Available presets
        'all_enabled' => [...],
        'all_disabled' => [...],
        'critical_only' => [...],
        'debug_mode' => [...],
    ],
],
```

#### Available Presets

[](#available-presets)

PresetDescriptionEvents Enabled`all_enabled`All events enabled (default)All`all_disabled`All events disabledNone`critical_only`Only critical events`otp_sent`, `otp_verified`, `otp_verification_failed``debug_mode`All events for debuggingAll### Event Listeners

[](#event-listeners)

Listen to OTP lifecycle events:

```
// app/Providers/EventServiceProvider.php

protected $listen = [
    \Bbim\Otpify\Events\OtpSending::class => [
        \App\Listeners\LogOtpSending::class,
    ],
    \Bbim\Otpify\Events\OtpSent::class => [
        \App\Listeners\LogOtpSent::class,
    ],
    \Bbim\Otpify\Events\OtpVerified::class => [
        \App\Listeners\HandleOtpVerified::class,
    ],
    \Bbim\Otpify\Events\OtpVerificationFailed::class => [
        \App\Listeners\LogOtpFailure::class,
    ],
];
```

Example listener:

```
namespace App\Listeners;

use Bbim\Otpify\Events\OtpSent;

class LogOtpSent
{
    public function handle(OtpSent $event): void
    {
        logger()->info('OTP sent', [
            'user' => $event->otpifiable->getKey(),
            'otp_id' => $event->otpifyCode->id,
            'driver' => $event->otpifyCode->driver,
        ]);
    }
}
```

### Command Line Control

[](#command-line-control)

Control events from the command line:

```
# Show current events status
php artisan otpify:events status

# Enable all events
php artisan otpify:events enable --all

# Disable all events
php artisan otpify:events disable --all

# Enable specific event
php artisan otpify:events enable --event otp_sent

# Disable specific event
php artisan otpify:events disable --event otp_sending

# Set preset
php artisan otpify:events preset --preset critical_only

# Reset to defaults
php artisan otpify:events reset
```

### Programmatic Control

[](#programmatic-control)

Control events programmatically:

```
use Bbim\Otpify\Helpers\EventController;

// Enable all events
EventController::enableAll();

// Disable all events
EventController::disableAll();

// Enable specific event
EventController::enableEvent('otp_sent');

// Disable specific event
EventController::disableEvent('otp_sending');

// Set preset
EventController::setPreset('critical_only');

// Check if event is enabled
if (EventController::isEventEnabled('otp_verified')) {
    // Event is enabled
}

// Get all events status
$status = EventController::getAllEventsStatus();

// Get full configuration
$config = EventController::getConfiguration();

// Apply custom configuration
EventController::applyConfiguration([
    'enabled' => true,
    'preset' => 'critical_only',
    'individual' => [
        'otp_sending' => false,
        'otp_verified' => true,
    ]
]);
```

### Performance Optimization

[](#performance-optimization)

Disable events you don't need to improve performance:

```
# Disable all events
OTPIFY_EVENTS_ENABLED=false

# Or use critical_only preset
OTPIFY_EVENTS_PRESET=critical_only

# Or disable specific events
OTPIFY_EVENT_OTP_SENDING=false
OTPIFY_EVENT_OTP_VERIFYING=false
```

🛡️ Rate Limiting
----------------

[](#️-rate-limiting)

Configure rate limiting in `.env`:

```
OTPIFY_RATE_LIMITING_ENABLED=true
OTPIFY_MAX_SENDS=3
OTPIFY_RATE_LIMIT_DECAY=10
OTPIFY_MAX_VERIFY_ATTEMPTS=5
OTPIFY_LOCKOUT_MINUTES=30
```

Disable for testing:

```
OTPIFY_RATE_LIMITING_ENABLED=false
```

🔧 Custom Models
---------------

[](#-custom-models)

Extend OTP models:

```
namespace App\Models;

use Bbim\Otpify\Models\OtpifyCode as BaseOtpifyCode;

class OtpifyCode extends BaseOtpifyCode
{
    // Add your custom methods
    public function user()
    {
        return $this->belongsTo(User::class, 'otpifiable_id');
    }

    public function scopeRecent($query)
    {
        return $query->where('created_at', '>=', now()->subMinutes(10));
    }
}
```

Register in config:

```
// config/otpify.php

'models' => [
    'otp_code' => \App\Models\OtpifyCode::class,
],
```

🧪 Testing
---------

[](#-testing)

Use fake drivers for testing:

```
// In your test
Otpify::shouldReceive('driver')
    ->with('fake_absher')
    ->andReturn(new \Bbim\Otpify\Drivers\FakeAbsherDriver());

$otpCode = Otpify::driver('fake_absher')->send($request, $user);
```

📊 Available Drivers
-------------------

[](#-available-drivers)

DriverDescriptionConfiguration`email`Send OTP via email`MAIL_*` settings`twilio`Send OTP via SMS`OTPIFY_TWILIO_*` settings`absher`Absher integration`ABSHER_*` settings`fake_absher`Fake Absher for testingTest configuration`authorization`Custom authorizationCustom implementation`fake_authorization`Fake authorization for testingTest configuration🔧 Configuration Options
-----------------------

[](#-configuration-options)

### Rate Limiting

[](#rate-limiting)

```
'rate_limiting' => [
    'enabled' => true,
    'max_sends' => 3,              // Max OTP sends per user
    'decay_minutes' => 10,         // Cooldown period
    'max_verify_attempts' => 5,    // Max verification attempts
    'lockout_minutes' => 30,       // Lockout duration
],
```

### Verification Methods

[](#verification-methods)

```
'verification_methods' => [
    'twilio' => 'phone',
    'email' => 'email',
    'absher' => 'email',
    // ... other drivers
],
```

🚨 Error Handling
----------------

[](#-error-handling)

The package provides specific exceptions for different scenarios:

- `OtpCodeNotFoundException`: OTP code not found
- `OtpCodeExpiredException`: OTP code has expired
- `OtpCodeIncorrectException`: Incorrect OTP code
- `OtpCodeAlreadyUsedException`: OTP code already used
- `TooManyOtpAttemptsException`: Rate limit exceeded
- `OtpifiableNotEqualAuthUserException`: User mismatch

📋 Requirements
--------------

[](#-requirements)

- PHP &gt;= 8.2
- Laravel &gt;= 10.0
- Propaganistas/LaravelPhone (for phone number validation)

🤝 Contributing
--------------

[](#-contributing)

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

📄 License
---------

[](#-license)

This package is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

🆘 Support
---------

[](#-support)

If you encounter any issues or have questions, please:

1. Check the [Issues](https://github.com/bbim/otpify/issues) page
2. Create a new issue with detailed information
3. Contact the maintainers

---

**Made with ❤️ by the BBIM team**

###  Health Score

17

—

LowBetter than 6% of packages

Maintenance44

Moderate activity, may be stable

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity13

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/959283206f4f6f4a4fa7f61c44c07ca3bfdb9648010ad4d50e2027579f1e14fc?d=identicon)[Ahmedd-Ibrahim](/maintainers/Ahmedd-Ibrahim)

---

Top Contributors

[![Ahmedd-Ibrahim](https://avatars.githubusercontent.com/u/21282328?v=4)](https://github.com/Ahmedd-Ibrahim "Ahmedd-Ibrahim (3 commits)")

### Embed Badge

![Health badge](/badges/bbim-otpify/health.svg)

```
[![Health](https://phpackages.com/badges/bbim-otpify/health.svg)](https://phpackages.com/packages/bbim-otpify)
```

###  Alternatives

[namshi/jose

JSON Object Signing and Encryption library for PHP.

1.8k99.6M101](/packages/namshi-jose)[league/oauth1-client

OAuth 1.0 Client Library

99698.8M106](/packages/league-oauth1-client)[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k2.9M88](/packages/bezhansalleh-filament-shield)[gesdinet/jwt-refresh-token-bundle

Implements a refresh token system over Json Web Tokens in Symfony

70516.4M35](/packages/gesdinet-jwt-refresh-token-bundle)[league/oauth2-google

Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client

41721.2M118](/packages/league-oauth2-google)[illuminate/auth

The Illuminate Auth package.

9327.3M1.0k](/packages/illuminate-auth)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
