Dynamic verification flows for login, registration, password reset, and account changes — driven by a composable scenario system.
ScenarioRegistry allows dynamic scenario registration at boot time. Each scenario defines its own authentication requirements, configuration keys, and post-OTP completion logic — keeping verification flows decoupled from the rest of the application.
1ScenarioRegistry::register('login_2fa', [2 'requires_auth' => true,3 'config_key' => 'verification.login_2fa',4 'completion' => LoginTwoFactorHandler::class,5 'channels' => ['sms', 'email'],6 'otp_length' => 6,7 'cooldown' => 60,8 'max_attempts' => 5,9]);10
11ScenarioRegistry::register('password_reset', [12 'requires_auth' => false,13 'config_key' => 'verification.password_reset',14 'completion' => PasswordResetHandler::class,15 'channels' => ['email'],16 'otp_length' => 6,17 'cooldown' => 120,18 'max_attempts' => 3,19]);VerificationService handles the full OTP lifecycle — from pending session creation and code generation to delivery, verification, and cooldown management. Multi-locale templates ensure users receive messages in their preferred language.
1$session = VerificationService::createPending(2 scenario: 'password_reset',3 identifier: $user->email,4 channel: 'email',5);6
7// Generate and deliver OTP8VerificationService::sendOtp($session, locale: 'en');9
10// Verify user input11$result = VerificationService::verify(12 session: $session,13 code: $request->input('code'),14);15
16// $result->status: 'verified' | 'invalid' | 'expired' | 'max_attempts'From user action to scenario completion — every step is deterministic, auditable, and scenario-driven.
User Action
Login, registration, password reset, or account change
Scenario Resolution
ScenarioRegistry resolves the matching scenario and its config
OTP Generation
Secure code generated with configured length and expiry
Delivery
OTP sent via SMS or Email using locale-aware templates
User Input
User submits the received verification code
Verification
Code validated with attempt tracking and expiry checks
Scenario Completion
Scenario-specific handler executes post-verification logic
User Action
Login, registration, password reset, or account change
Scenario Resolution
ScenarioRegistry resolves the matching scenario and its config
OTP Generation
Secure code generated with configured length and expiry
Delivery
OTP sent via SMS or Email using locale-aware templates
User Input
User submits the received verification code
Verification
Code validated with attempt tracking and expiry checks
Scenario Completion
Scenario-specific handler executes post-verification logic
The building blocks that make verification secure, flexible, and user-friendly across every scenario.
OTP codes are delivered via SMS and Email. Each scenario declares its supported channels — users receive verification through the most appropriate medium.
Every verification attempt is tracked per session. After exceeding the configured maximum, the session is locked — providing built-in brute-force protection.
Configurable cooldown periods prevent repeated OTP requests. Users must wait before requesting a new code, reducing abuse and delivery costs.
Verification messages are rendered from locale-specific templates. Currently supporting TR, EN, DE, and FR with straightforward extension for new languages.
Each verification flow is bound to a unique session token. Tokens are time-limited and single-use, ensuring isolation between concurrent verification attempts.
Post-verification logic is fully decoupled. Each scenario registers its own completion handler — login grants a token, registration activates the account, password reset issues a reset link.