3neti / form-handler-otp
OTP verification handler for form flow system
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/3neti/form-handler-otp
Requires
- php: ^8.2
- 3neti/form-flow: ^1.0
- illuminate/support: ^12.0
- spatie/laravel-data: ^4.0
Requires (Dev)
- inertiajs/inertia-laravel: ^2.0
- orchestra/testbench: ^10.3
- pestphp/pest: ^3.8
- pestphp/pest-plugin-laravel: ^3.0
README
A Form Flow Manager plugin for OTP (One-Time Password) verification using time-based tokens (TOTP).
Features
✅ Time-based OTP generation (TOTP RFC 6238)
✅ Configurable OTP period, digits, and resend limits
✅ SMS delivery via callback (provider-agnostic)
✅ Resend functionality with cooldown timer
✅ Automatic cache management
✅ Auto-registration with Form Flow Manager
✅ Mobile-optimized Vue component
Installation
composer require 3neti/form-handler-otp
That's it! The handler automatically registers itself with the Form Flow Manager.
Configuration
Publish the config file:
php artisan vendor:publish --tag=otp-handler-config
Publish the Vue component:
php artisan vendor:publish --tag=otp-handler-stubs
Edit config/otp-handler.php:
return [ 'label' => env('OTP_LABEL', config('app.name')), 'period' => env('OTP_PERIOD', 600), // 10 minutes 'digits' => env('OTP_DIGITS', 4), 'cache_prefix' => 'otp', 'max_resends' => env('OTP_MAX_RESENDS', 3), 'resend_cooldown' => env('OTP_RESEND_COOLDOWN', 30), // 30 seconds 'send_sms_callback' => null, // Configure in service provider ];
Environment Variables
Add to .env:
OTP_LABEL="Your App" OTP_PERIOD=600 # 10 minutes OTP_DIGITS=4 # 4-digit OTP OTP_MAX_RESENDS=3 # Max 3 resend attempts OTP_RESEND_COOLDOWN=30 # 30 seconds cooldown
SMS Integration
The plugin includes built-in SMS support via lbhurtado/sms with EngageSpark. No additional configuration needed if your app already uses EngageSpark.
Environment Variables:
ENGAGESPARK_API_KEY=your_api_key ENGAGESPARK_ORG_ID=your_org_id ENGAGESPARK_SENDER_ID=cashless
The plugin automatically uses these credentials from your existing SMS configuration.
Usage
In a Form Flow
const response = await fetch('/form-flow/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reference_id: 'unique-id', steps: [ { handler: 'otp', config: { title: 'Verify Your Identity', description: 'Enter the OTP sent to your mobile', max_resends: 3, resend_cooldown: 30, digits: 4 } } ], callbacks: { on_complete: 'https://your-app.test/callback' } }) });
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
max_resends |
integer | 3 |
Maximum OTP resend attempts |
resend_cooldown |
integer | 30 |
Cooldown between resends (seconds) |
digits |
integer | 4 |
Number of digits in OTP (4, 5, or 6) |
Context Requirements
The OTP handler requires mobile to be present in the context:
// In your form flow $context = [ 'flow_id' => $flowId, 'mobile' => '09171234567', // Required for OTP delivery ];
Collected Data
The handler returns the following data structure:
[
'mobile' => '09171234567',
'otp_code' => '1234',
'verified_at' => '2024-12-15T08:00:00+08:00',
'reference_id' => 'flow-abc123',
]
Testing
cd packages/form-handler-otp composer test
Test Coverage
- ✅ Interface implementation
- ✅ OTP generation (4, 5, 6 digits)
- ✅ OTP validation (correct/incorrect)
- ✅ Cache management
- ✅ Expiry handling
- ✅ Config schema
- ✅ Handler name
How It Works
1. Plugin Auto-Registration
// OtpHandlerServiceProvider::boot() protected function registerHandler(): void { $handlers = config('form-flow.handlers', []); $handlers['otp'] = OtpHandler::class; config(['form-flow.handlers' => $handlers]); }
2. OTP Generation
Uses spomky-labs/otphp library for TOTP (Time-based One-Time Password):
$totp = TOTP::createFromSecret($secret); $totp->setPeriod(600); // 10 minutes $totp->setDigits(4); // 4-digit code $code = $totp->now(); // Generate current OTP
3. SMS Delivery
OTP is sent via configurable callback:
$callback = config('otp-handler.send_sms_callback'); $callback($mobile, $otpCode, $appName);
4. Validation
$totp = TOTP::createFromSecret($cachedSecret); $isValid = $totp->verify($submittedCode, null, $window); // ±1 time window
5. Resend Logic
Frontend handles resend with:
- Cooldown timer (default 30 seconds)
- Max attempts limit (default 3)
- Success/error messaging
Architecture
This is a plugin package for Form Flow Manager:
form-handler-otp/ (Plugin)
├── Implements FormHandlerInterface
├── Self-registers via service provider
└── Optional dependency
form-flow-manager/ (Core)
├── Discovers plugins automatically
└── Orchestrates flow with registered handlers
redeem-x/ (Host App)
└── Installs: core + chosen plugins
Plugin Benefits
✅ Optional - Install only if needed
✅ Independent - Tested separately
✅ Reusable - Works across different apps
✅ Maintainable - Clean separation of concerns
✅ Provider-agnostic - No hardcoded SMS dependencies
Security Considerations
- Rate Limiting: Max 3 resends per session
- Timing Attack Prevention: Constant-time comparison via TOTP library
- Clock Skew: ±1 time window for validation
- Cache Isolation: Unique prefix to avoid collisions
- Auto-cleanup: Cache TTL matches OTP period
- One-time use: Cache cleared after successful validation
Requirements
- PHP 8.2+
- Laravel 12+
- Form Flow Manager (
lbhurtado/form-flow-manager) - Spomky Labs OTPHP (
spomky-labs/otphp)
Troubleshooting
"Handler not found: otp"
Solution:
php artisan config:clear php artisan cache:clear composer dump-autoload
OTP not sent
Check:
- SMS callback configured?
- Mobile number valid?
- SMS provider credentials correct?
- Check logs for callback errors
OTP validation fails
Check:
- OTP not expired? (default: 10 minutes)
- Correct digits configured?
- Cache working properly?
- Clock sync between server and client?
Related Packages
- form-flow-manager - Core orchestration
- form-handler-location - GPS capture
- form-handler-selfie - Camera capture (planned)
- form-handler-signature - Digital signature (planned)
License
MIT
Author
3neti