定制 prasanth-j/otpify 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

prasanth-j/otpify

Composer 安装命令:

composer require prasanth-j/otpify

包简介

A secure, flexible OTP package for Laravel. Supports database and cache drivers, multiple purposes, hashed storage, events, and validation rules.

README 文档

README

Build Status Total Downloads Latest Version on Packagist License: MIT

A secure, flexible OTP (One-Time Password) package for Laravel. Supports database and cache storage drivers, multiple purposes per identifier, hashed token storage, events, and a ready-made validation rule.

Requirements

  • PHP 8.1+
  • Laravel 9, 10, 11, 12, or 13

Each Laravel major has its own PHP range and support window (per Laravel's official support policy), so the actual floor depends on which Laravel version you're on:

Laravel PHP range Security fixes until
9 8.0 – 8.2 (this package requires 8.1+) Feb 2024 — EOL
10 8.1 – 8.3 Feb 2025 — EOL
11 8.2 – 8.4 Mar 2026 — EOL
12 8.2 – 8.5 Feb 2027
13 8.3 – 8.5 Mar 2028

Laravel 9, 10, and 11 no longer receive security fixes upstream. This package still works on them, but for new projects prefer Laravel 12 or 13.

Installation

composer require prasanth-j/otpify

Publish the config file:

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

If you plan to use the database driver (the default), publish and run the migration:

php artisan vendor:publish --tag=otpify-migrations
php artisan migrate

Configuration

config/otpify.php:

return [
    'driver'   => env('OTPIFY_DRIVER', 'database'),
    'digits'   => env('OTPIFY_DIGITS', 6),
    'validity' => env('OTPIFY_VALIDITY', 10), // minutes
    'type'     => env('OTPIFY_TYPE', 'numeric'), // numeric|alpha|alphanumeric
    'cache'    => [
        'prefix' => 'otpify',
        'store'  => env('OTPIFY_CACHE_STORE', null), // null = default cache store
    ],
];
Key Description
driver database or cache.
digits Default OTP length (4-8). Can be overridden per call.
validity Default expiry time in minutes. Can be overridden per call.
type Default character set: numeric, alpha, or alphanumeric.
cache.prefix Prefix used to build cache keys (otpify:{identifier}:{purpose}).
cache.store Which cache store to use. null uses your app's default store.

Security

  • OTPs are generated with random_int(), never mt_rand().
  • Only the SHA-256 hash of the OTP is stored (hash('sha256', $token)) — the plaintext is never persisted.
  • Validation compares hashes with hash_equals() for constant-time comparison.
  • The plaintext token is only ever returned once, from generate()/resend(), so you can send it via email/SMS/etc. validate() never returns the token.
  • OTPs are single-use: a successful validate() call immediately invalidates the OTP.

Usage

Generate an OTP

use PrasanthJ\Otpify\Facades\Otpify;

$result = Otpify::generate('user@example.com'); // purpose defaults to "default"

$result->token;     // e.g. "482913" — send this to the user
$result->expiresAt; // Carbon instance
$result->status;    // "generated"

With a purpose and custom options:

$result = Otpify::generate('user@example.com', 'login', [
    'digits'   => 4,
    'validity' => 5,          // minutes
    'type'     => 'alphanumeric',
]);

Generating a new OTP for the same identifier + purpose deletes any previous one — only the latest OTP for a given identifier/purpose is ever valid.

Validate an OTP

$result = Otpify::validate('user@example.com', $token, 'login');

if ($result->isValid()) {
    // proceed
}

OtpResult status values and helpers:

Status Helper Meaning
valid isValid() Token matched, unused, not expired.
invalid isInvalid() Token does not match.
expired isExpired() Token matched an OTP that has expired.
already_used isAlreadyUsed() OTP was already validated once.
not_found No OTP exists for that identifier/purpose.
generated wasGenerated() Returned only by generate()/resend().

Invalidate an OTP

Otpify::invalidate('user@example.com', 'login'); // returns bool

Resend an OTP

Invalidates any existing OTP for the identifier/purpose and generates a fresh one:

$result = Otpify::resend('user@example.com', 'login', ['validity' => 10]);

Multiple purposes

Every method accepts a purpose so the same identifier can hold independent OTPs at once, e.g. 'login', '2fa', 'password-reset'.

Otpify::generate('user@example.com', 'login');
Otpify::generate('user@example.com', '2fa');

Validation rule

Use OtpRule to validate an OTP as part of a normal Laravel form request or validator:

use Illuminate\Support\Facades\Validator;
use PrasanthJ\Otpify\Rules\OtpRule;

$validator = Validator::make($request->all(), [
    'otp' => ['required', new OtpRule($request->input('email'), 'login')],
]);

Events

Event Fired when
PrasanthJ\Otpify\Events\OtpGenerated An OTP is generated. Has identifier, purpose, token, expiresAt.
PrasanthJ\Otpify\Events\OtpValidated An OTP passes validation. Has identifier, purpose.
PrasanthJ\Otpify\Events\OtpFailed An OTP fails validation. Has identifier, purpose, reason (the failure status).
use PrasanthJ\Otpify\Events\OtpGenerated;

Event::listen(function (OtpGenerated $event) {
    // send $event->token to $event->identifier via SMS/email
});

Drivers

Database (default)

Stores hashed OTPs in the otpify_tokens table. Requires the migration to be run. Good default choice — durable, works with the otpify:clean command.

Cache

Stores hashed OTPs in your configured cache store under otpify:{identifier}:{purpose}, using the OTP's expiry as the cache TTL. No migration needed; good fit if you already run Redis/Memcached and don't need a durable audit trail.

OTPIFY_DRIVER=cache
OTPIFY_CACHE_STORE=redis

Rate limiting

Otpify does not rate limit generation/validation attempts itself — pair it with Laravel's RateLimiter:

use Illuminate\Support\Facades\RateLimiter;
use PrasanthJ\Otpify\Facades\Otpify;

$key = 'otp-generate:' . $request->ip() . ':' . $request->input('email');

if (RateLimiter::tooManyAttempts($key, 5)) {
    abort(429, 'Too many OTP requests. Please try again later.');
}

RateLimiter::hit($key, 60); // 1 attempt per minute window, 5 max

$result = Otpify::generate($request->input('email'));

Do the same around Otpify::validate() to slow down brute-force guessing attempts.

Cleaning up expired tokens

For the database driver, expired and used rows accumulate over time. Clean them up with:

php artisan otpify:clean

Schedule it in routes/console.php (Laravel 11+) or app/Console/Kernel.php (Laravel 9/10):

Schedule::command('otpify:clean')->daily();

This command is a no-op (with a warning) when the cache driver is active, since cache entries expire on their own via TTL.

Migrating from v1

v2.0.0 is a full rewrite and breaking change:

  • The otps table and Otp model are gone, replaced by the otpify_tokens table (no model).
  • Otpify::generate()/validate() now return an OtpResult object instead of an array.
  • Otpify::generate() signature changed from generate(string $identifier, int $userId = null, string $otpType = null, int $digits = null, int $validity = null) to generate(string $identifier, string $purpose = 'default', array $options = []).
  • otpType is now purpose, and is a required-shaped concept everywhere (defaults to 'default').
  • OTPs are now stored hashed (SHA-256) instead of in plaintext.
  • user_id is no longer stored — key OTPs by whatever identifier makes sense for you (email, phone, user ID as a string, etc.).

If you have existing data in the old otps table, write a one-off migration to hash and copy identifier/otp_type/token/expiry data into otpify_tokens, or simply let outstanding OTPs expire naturally and drop the old table.

Development

composer test      # run the Pest test suite
composer lint       # fix code style with Laravel Pint
composer lint:test  # check code style without fixing
composer analyse    # run static analysis with Larastan

Larastan 3.x requires PHP 8.2+ locally to run static analysis, even though the package itself supports PHP 8.1+.

License

MIT

prasanth-j/otpify 适用场景与选型建议

prasanth-j/otpify 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 39 次下载、GitHub Stars 达 2, 最近一次更新时间为 2022 年 08 月 05 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「One Time Password」 「laravel otp」 「otpify」 「otp validation」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 prasanth-j/otpify 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 prasanth-j/otpify 我们能提供哪些服务?
定制开发 / 二次开发

基于 prasanth-j/otpify 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 39
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 11
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 2
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2022-08-05