3neti / form-handler-otp
OTP verification handler for form flow system
Requires
- php: ^8.2
- 3neti/form-flow: ^1.8
- illuminate/support: ^12.0 || ^13.0
- spatie/laravel-data: ^4.0
Requires (Dev)
- inertiajs/inertia-laravel: ^2.0 || ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^10.3 || ^11.0
- pestphp/pest: ^3.8 || ^4.0
- pestphp/pest-plugin-laravel: ^3.2 || ^4.0
README
3neti/form-handler-otp adds mobile one-time-password verification to
3neti/form-flow. It delegates OTP generation, delivery, expiry, attempt
tracking, and verification to a configured Txtcmdr service.
Requirements
- PHP 8.2 or newer
- Laravel 12 or 13
- Form Flow 1.8 or newer
- Inertia Laravel 2 or 3 in applications that render the published Vue page
Installation
composer require 3neti/form-handler-otp php artisan otp-handler:install --no-interaction
The package service provider registers the otp handler automatically. The
install command publishes the package configuration and Vue page.
Configuration
Publish the configuration independently when needed:
php artisan vendor:publish --tag=otp-handler-config
Configure the Txtcmdr endpoint and bearer token in the host environment:
TXTCMDR_API_URL=https://txtcmdr.example TXTCMDR_API_TOKEN= TXTCMDR_CONNECT_TIMEOUT=5 TXTCMDR_TIMEOUT=15 TXTCMDR_VERIFY_SSL=true OTP_HANDLER_DRIVER=txtcmdr OTP_MAX_RESENDS=3 OTP_RESEND_COOLDOWN=30
Do not commit the API token. The handler sends it only as the bearer token for Txtcmdr requests.
Form Flow contract
Add an otp step after a step that collects mobile:
[
'handler' => 'otp',
'config' => [
'purpose' => 'onboarding.account',
'max_resends' => 3,
'resend_cooldown' => 30,
'digits' => 6,
'ui_variant' => 'compact',
],
]
The handler reads the mobile number from the current Form Flow session. It fails before contacting Txtcmdr if no mobile number has been collected.
Rendering the handler has no delivery side effect. The claimant explicitly selects Send verification code. The handler then:
- creates a challenge through
POST /api/v1/otp/challenges; - stores only the returned challenge reference in the session;
- enables verification and the throttled resend control.
On submission, it verifies through
POST /api/v1/otp/challenges/{challenge}/verify. A successful verification
clears the challenge and delivery-control session state, then
returns:
[
'mobile' => '+639171234567',
'verified_at' => '2026-07-31T15:00:00+08:00',
'reference_id' => 'flow-123',
'verification_reference' => '01K...',
'verification_purpose' => 'onboarding.account',
]
The raw code is never returned as Form Flow result data. Hosts must validate the structured proof against the expected purpose and claimant identity before executing a protected workflow.
User interface
The published Vue page supports Form Flow's default, compact, and
immersive UI variants. It uses the shared Form Flow screen and action
components, accepts numeric input, submits the nested data.otp_code payload,
and focuses the code input on entry.
Delivery safeguards
- Verification identifiers are isolated by Form Flow reference in the session.
- Opening or refreshing the page cannot send an SMS.
- Successful codes are one-time because Txtcmdr is the verification authority and local session state is cleared after acceptance.
- Resend count and cooldown are enforced by the server handler, not only by the browser.
- Provider rejection reasons are mapped to sanitized validation messages.
- A missing verification session or mobile number fails closed.
- Hosts should still rate-limit Form Flow endpoints by authenticated principal, session, mobile reference, and source address.
Testing
composer test
composer pint -- --test
composer audit
The package suite fakes HTTP and verifies the request, session, resend, provider-rejection, expiry, and UI render contracts without sending SMS.
License
MIT