NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Two-Factor for Laravel

Trait verbs and actions

HasTwoFactorAuthentication exposes the same lifecycle on the user itself. Every verb delegates to TwoFactor::for($this) — never straight to an action — so TwoFactor::fake() records it and your action overrides still apply:

$setup  = $user->startTwoFactorEnrolment();          // → TwoFactorSetup (== ->start())
$user->confirmTwoFactor($code);                      // == ->confirm()
$result = $user->attemptTwoFactorCode($code);        // → VerificationResult (== ->attempt())
$ok     = $user->verifyTwoFactorCode($code);         // bool (== ->attempt()->verified)
$user->disableTwoFactor();                           // == ->disable()
$codes  = $user->regenerateTwoFactorRecoveryCodes(); // == ->recoveryCodes()->regenerate()

$user->twoFactorRecoveryCodesRemaining();            // int
MethodReturnsPurpose
startTwoFactorEnrolment(?string $label = null, ?string $issuer = null)TwoFactorSetupBegin (or restart) a pending enrolment — ->start().
confirmTwoFactor(string $code)voidConfirm the pending enrolment with the first code — ->confirm().
attemptTwoFactorCode(string $code)VerificationResultLogin challenge — TwoFactor::for($this)->attempt().
verifyTwoFactorCode(string $code)boolLogin challenge — exactly ->attempt($code)->verified.
disableTwoFactor()voidClear all two-factor state — ->disable().
regenerateTwoFactorRecoveryCodes()list<string>Replace the recovery codes, returning the new set once — ->recoveryCodes()->regenerate().
twoFactorRecoveryCodesRemaining()intHow many single-use recovery codes remain.
hasTwoFactorEnabled()boolA secret is set and confirmed.
hasPendingTwoFactor()boolA secret is set but not yet confirmed.
twoFactorSecret()?stringThe decrypted base32 secret.
twoFactorRecoveryCodes()list<string>The stored codes — hashes in hashed mode.
twoFactorLabel()stringThe provisioning label — email, then primary key. Override to customise.
twoFactorCasts()array<string, string>The casts to spread into the model’s casts().

Pass a label and/or an issuer to startTwoFactorEnrolment(), or override twoFactorLabel() on the model to key the provisioning URI on a username or phone instead of the default (email, then primary key).

The actions behind them

Each write verb ends in one action class — StartEnrolment, ConfirmEnrolment, AttemptTwoFactorCode, RegenerateRecoveryCodes or DisableTwoFactor. Call those directly when composing your own actions or jobs; the full mapping is in DI and actions.

Disabling clears every two-factor column — secret, recovery codes, confirmation and the replay marker — so an admin can reset a compromised account in one call and the user can re-enrol cleanly.

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 crypto

By 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.