jtargosz/laravel-action-otp
jtargosz/laravel-action-otp is a Laravel package for one-time password workflows, offering action-based helpers to generate, send, and verify OTP codes. It fits Laravel's action pattern for clean, reusable authentication and verification flows.
Architecture fit: The package remains a conventional Laravel OTP/verification package: a service provider, config file (otp.confirm, purposes), actions (ActionOtp), validation rules (ValidOtpCode), controllers for code/magic-link/verify/resend routes, a session-confirmation concern (ConfirmSession), and a fake (ActionOtpFake) for test suites. v2.1.0 strengthens the server-authoritative posture — ActionOtp::identifyUsing() lets the application decide which identifier (email, phone, etc.) the verify and resend routes operate on, discarding the client-supplied request field. That is a meaningful design direction for multi-identifier or multi-tenant applications where the request payload should not be trusted to select the verification channel.
Integration feasibility: Low-to-moderate effort. Standard package install plus route/config publication. The identifyUsing() resolver is optional and additive — existing single-identifier applications can adopt it later or never. The ActionOtpFake constructor change is trivial to absorb in test suites (typically resolved from the container rather than manually constructed).
Technical risk: Two items carry consumer-facing risk despite the minor version bump:
ConfirmSession semantics moved. Session marking now happens inside the package controllers rather than in ConfirmSession::handle() via the global request(). Applications that call ConfirmSession::handle() directly outside package routes (custom controllers, API endpoints, queued flows, Livewire actions) must now set ConfirmSession::SESSION_KEY themselves. Existing manual verifications that relied on the old implicit session write will silently stop marking the session — a plausible auth-bypass-adjacent or "prompt again on every request" bug depending on how the flag is consumed.ActionOtpFake constructor signature. Removal of the unused Recorder argument breaks any test helper that passes it positionally.Neither is a deprecation cycle; both are effective breaking changes shipped as a patch/minor. The ValidOtpCode blank-purpose fix is a behavior change for anyone relying on the prior exception, though the new behavior (validation failure) is strictly better.
Key questions:
ConfirmSession::handle() outside the package's routes? If yes, we must set SESSION_KEY manually as part of the upgrade.ActionOtpFake with a Recorder argument?identifyUsing() is a security hardening opportunity and should be scheduled.null-resolver path mean for our UX when a user supplies a stale/unknown identifier — the release notes say it answers empty without counting an attempt, which could be an enumeration-friendly behavior worth threat-modeling before we adopt it.Stack fit: Framework-native, no infrastructure additions required. Works with the existing session driver, cache/rate-limiter for attempt counting, and notification channels already configured. The server-side identifier resolver is compatible with our multi-identifier login flow (email + phone) and with tenant-scoped identifiers resolved from the authenticated context rather than the payload.
Migration path: Treat v2.1.0 as a minor with breaking edges rather than a drop-in patch.
ConfirmSession usages outside the package routes; patch each to set SESSION_KEY explicitly.ActionOtpFake construction; drop the Recorder argument.identifyUsing() in a follow-up change once (1) and (2) are green, so the two concerns are not conflated in one PR.Compatibility: PHP/Laravel constraints unchanged by these notes. The behavioral changes are the compatibility surface, not the runtime matrix. Pin to ^2.1 once patched; avoid >=2.0 ranges so a future patch does not reintroduce silent behavior shifts.
Sequencing: Ship the mechanical fixes (1) and (2) as a single dependency-bump PR with tests proving session confirmation still works on our custom paths. Then, in a separate PR, migrate to identifyUsing() and add a test asserting the request field is ignored for verify and resend. Defer the null-resolver (empty, uncounted) path until the product and security review on enumeration behavior is settled.
Maintenance: Low ongoing burden — one config file, one migration-free package, translations maintained upstream. The main maintenance cost is vigilance: this release shows the package will make behavioral changes in minor versions, so we should pin conservatively and read changelogs rather than relying on semver.
Support: No new support surface. The identifyUsing() resolver adds a small amount of application-owned logic that we now own and must document for support — specifically what happens when the resolver returns null (user sees empty, no attempt counted). Support scripts should know that a user reporting "nothing happens, no lockout" may be hitting this path.
Scaling: Neutral to slightly positive. Server-side identifier resolution removes a class of payload-tampering and reduces wasted attempt-counter writes for unresolvable identifiers, which marginally reduces cache/limiter churn on verification endpoints. No change to notification or storage scaling.
Failure modes:
ActionOtpFake signature — fails loudly at test time, low severity, caught in CI.empty responses — an attacker probing identifiers will not trip attempt counters; this is a deliberate design choice but should be rate-limited at the edge.Ramp-up: Minimal. A developer familiar with the package needs under a day to apply the mechanical fixes and validate; the identifyUsing() migration is a small follow-on task with a clear test target. The more important ramp-up is process: establish a habit of diffing the changelog against our call sites of ConfirmSession and the fake, since both have now proven to be volatile surfaces.
How can I help you explore Laravel packages today?