Model setup
Implement the contract, use the trait, and spread twoFactorCasts() into your model’s casts:
use Illuminate\Foundation\Auth\User as Authenticatable;
use RoundlyConsulting\TwoFactor\Concerns\HasTwoFactorAuthentication;
use RoundlyConsulting\TwoFactor\Contracts\TwoFactorAuthenticatable;
final class User extends Authenticatable implements TwoFactorAuthenticatable
{
use HasTwoFactorAuthentication;
protected function casts(): array
{
return [
// ...your other casts
...$this->twoFactorCasts(),
];
}
}twoFactorCasts() applies encrypted to the secret, datetime to confirmed_at, and array (hashed mode, the default) or encrypted:array (encrypted mode) to the recovery codes. Column names are always read from two-factor.columns, so a remapped schema just works.
Hidden from serialization
The trait pushes the secret, recovery-code and last-used-timestep columns into the model’s $hidden automatically, so returning the user from a route never leaks the decrypted secret or codes:
return $user; // from a route — no secret, no recovery codes
$user->toArray(); // two_factor_secret, two_factor_recovery_codes and
$user->toJson(); // two_factor_last_used_timestep are always hidden
$user->two_factor_confirmed_at; // stays visible for UI stateState helpers
$user->hasTwoFactorEnabled(); // secret set + confirmed
$user->hasPendingTwoFactor(); // secret set, not yet confirmed
$user->twoFactorSecret(); // ?string
$user->twoFactorRecoveryCodes(); // list<string>
$user->twoFactorRecoveryCodesRemaining(); // int
$user->twoFactorLabel(); // provisioning label (email → primary key)An enabled user has a secret and a confirmed_at timestamp; a pending user has a secret but has not confirmed it yet. Only an enabled user can pass a login challenge. In hashed mode, twoFactorRecoveryCodes() returns the stored hashes, not plaintext. For the whole state in one read, use TwoFactor::for($user)->status() — see The TwoFactor facade.
Provisioning label
The label inside the otpauth:// URI defaults to the email attribute, then the primary key. Override twoFactorLabel() to key it on a username or phone instead:
final class User extends Authenticatable implements TwoFactorAuthenticatable
{
use HasTwoFactorAuthentication;
// Key the otpauth:// label on a username instead of the email.
public function twoFactorLabel(): string
{
return $this->username;
}
}Database columns
| Column (default name) | Type | Notes |
|---|---|---|
two_factor_secret | text, nullable | Encrypted base32 secret. Hidden from serialization. |
two_factor_recovery_codes | text, nullable | Hashed (default) or encrypted code list. Hidden from serialization. |
two_factor_confirmed_at | timestamp, nullable | Set when enrolment is confirmed. Stays visible for UI state. |
two_factor_last_used_timestep | unsignedBigInteger, nullable | Read and written by the column replay guard. Hidden from serialization. |
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.