Skip to content

otp vault

alizeinodin edited this page Sep 11, 2026 · 2 revisions

OTP vault

Optional module: generate → HMAC-SHA256 store → deliver → check.

Enable

php artisan smsapi:install otp
SMSAPI_OTP_VAULT_ENABLED=true
SMSAPI_OTP_LENGTH=5
SMSAPI_OTP_TTL=300
SMSAPI_OTP_MAX_ATTEMPTS=5
# SMSAPI_OTP_VAULT_STORE=database  # or cache
# SMSAPI_OTP_PEPPER=               # defaults to APP_KEY

Issue + verify

use Alizeinodin\SmsApi\Facades\Sms;

// purpose = vault namespace ("login"); template = SMS/mail template name
Sms::challenge('login')
    ->template('otp-login')
    ->to($user)
    ->send();

Sms::challenge('login')->check($user, $request->input('code'));

$result = Sms::challenge('login')->check($user, $code, throw: false);
if ($result->ok()) {
    // …
}

Security model

Rule Behavior
Storage HMAC only — never plaintext in DB
Binding Hash includes recipient + purpose + code
Consume Atomic — second concurrent success fails
Attempts Incremented on mismatch; lock after max
TTL Expired challenges rejected
Re-issue Invalidates previous active challenge
Throttle Per recipient (+ purpose) on issue
Sync default Avoids putting CODE on the queue

Queued delivery (opt-in)

SMSAPI_OTP_VAULT_ALLOW_QUEUE=true
Sms::challenge('login')->template('otp-login')->to($user)->queue('otp')->send();

If the job permanently fails, the challenge is invalidated via SendMessageJob::failed().

Danger: Queued vault sends still serialize CODE into the job payload. Prefer sync for production OTP.

Naming

API Meaning
Sms::otp('tpl') Deliver template (you provide CODE)
Sms::challenge('purpose') Vault lifecycle
Sms::otpVault() Low-level vault service

Clone this wiki locally