Brute-force limiter
TwoFactor::for($user)->attempt() throttles per user out of the box. Every attempt is counted before it is verified, in one atomic increment, so even a burst of parallel guesses gets at most attempts.max (default 5) verifications per attempts.decay seconds (default 60). Past that, it throws TwoFactorRateLimitedException without doing any verification work and dispatches TwoFactorRateLimited. A successful verification clears the counter.
use RoundlyConsulting\TwoFactor\Exceptions\TwoFactorRateLimitedException;
use RoundlyConsulting\TwoFactor\Facades\TwoFactor;
try {
$result = TwoFactor::for($user)->attempt($request->string('code')->toString());
} catch (TwoFactorRateLimitedException $e) {
// too many failed attempts — retry after $e->secondsUntilAvailable seconds
}
if ($result->verified) {
// accepted — TOTP (replay-safe) or a single-use recovery code
}Configuration
// config/two-factor.php
'attempts' => [
'max' => 5, // failed attempts before lockout
'decay' => 60, // seconds the lockout lasts
],
// or hand throttling to your own throttle: middleware
'attempts' => null,Set attempts to null to disable the limiter entirely and use your own throttle: middleware instead. Both values must be integers of at least 1 — anything lower, or not an integer, throws InvalidTwoFactorConfigException. Only null switches the limiter off; false, 'off' or 0 throws, and a blank value keeps the shipped limits.
How attempts are counted
- Keyed per account as two-factor:{model class}:{id} through Laravel’s RateLimiter, so a user and a client with the same id never share a lockout.
- The count is taken before verification, so parallel requests can’t slip extra guesses past the limit. It stands unless the code passes — a wrong code and a replayed code both stay counted.
- A user without a confirmed enrolment fails before the limiter is consulted.
- TwoFactorRateLimitedException carries secondsUntilAvailable, and so does the TwoFactorRateLimited event — use it for a “try again later” state or an alert.
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.