Open source
Two-Factor for Laravel
composer require roundly-consulting/two-factor-for-laravelOverview
Native two-factor authentication for Laravel, built on the RFC 6238 TOTP standard every authenticator app speaks. It generates the secret and the otpauth:// provisioning URI, confirms enrolment, verifies login codes with constant-time comparison and atomic replay protection, and issues single-use recovery codes for a lost phone — all through TwoFactor::for($user). Secrets are encrypted at rest, recovery codes hashed, and a per-user brute-force limiter is on by default. MIT-licensed and dependency-light: the TOTP maths comes from our own crypto-for-laravel — no third-party crypto or QR library in the security path.
What you get
Guided enrolment
Start returns the secret, the otpauth:// URI and the recovery codes exactly once; 2FA switches on only after the first code confirms it.
Replay-safe verification
Constant-time comparison and an atomic timestep claim — an intercepted code can’t be used twice, even by two racing requests.
Single-use recovery codes
Hashed by default, consumed under a row lock, and every attempt reports how many codes the user has left.
Built-in brute-force limiter
Five attempts a minute per account by default, each counted before it is verified — with a typed exception, an event and a one-line opt-out.
Encrypted and hidden
The secret is encrypted with your APP_KEY, and the sensitive columns never appear in toArray() or a JSON response.
Standards-compatible
The SHA1, 6-digit, 30-second RFC 6238 profile by default — secrets from another library keep their codes, so nobody re-enrols; the stored columns are re-encrypted once.
Facade, DI or actions
TwoFactor::for($user), the injected TwoFactorService or one action class — and TwoFactor::fake() records every call in your tests.
Documentation
Installation
Install via Composer, publish the migration that adds four nullable columns, and optionally publish the config.
Configuration
Every config/two-factor.php key and default — TOTP parameters, issuer, recovery codes, limiter, replay guard, table and columns.
Model setup
Implement TwoFactorAuthenticatable, use the HasTwoFactorAuthentication trait and spread twoFactorCasts() into your casts.
The TwoFactor facade
One entry point: TwoFactor::for($user) for enrolment, login challenges, status, recovery codes and disabling, plus the flat TOTP primitives.
DI and actions
Inject the TwoFactorService contract instead of calling the facade, or run a single action — the same code path, and the fake still applies.
Enrolment
Start a pending enrolment, show the secret, QR URI and recovery codes once, then confirm it with the first authenticator code.
Rendering the QR code
Turn the otpauth:// provisioning URI into a QR code — server-side as SVG with qr-for-laravel, or client-side in your front end.
Verifying at login
Check a challenge code with TwoFactor::for($user)->attempt() — TOTP with replay protection first, then a single-use recovery code.
Recovery codes
Single-use backup codes for a lost device — generated on enrolment, stored hashed by default, consumed atomically, regenerable.
Brute-force limiter
A per-user throttle counted before every check — five attempts a minute by default — with an exception, an event and an opt-out.
Replay protection
Every accepted code claims its timestep atomically, so an intercepted code can’t be reused — tracked in a column, the cache or your own guard.
Trait verbs and actions
The whole lifecycle as verbs on the user model — sugar over TwoFactor::for($this), so the fake records them and the same actions run.
Events
Nine events across enrolment, verification, recovery codes, replays and lockouts — each carries the user, never a secret or a code.
Exceptions
One TwoFactorException base class and seven typed failures — wrong code, enrolment state, rate limit, bad config and more.
Security model
Encrypted secrets, hashed recovery codes, constant-time checks, atomic replay and recovery-code guards, and bounds-checked config.
Migrating from another TOTP library
Secrets from any RFC 6238 library keep their codes, so nobody re-enrols — re-encrypt the stored columns once. Fortify migration included.
Testing
TwoFactor::fake() swaps in a programmable, no-crypto double that records every call — or drive real codes against a frozen clock.
Requirements
PHP 8.4+, Laravel 12 or 13 and an APP_KEY — with our crypto, enums and package-toolkit packages as dependencies.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.