---
title: "Model setup — Two-Factor for Laravel | Roundly"
description: "Implement TwoFactorAuthenticatable, use the HasTwoFactorAuthentication trait and spread twoFactorCasts() into your casts."
url: https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/model-setup
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Two-Factor for Laravel](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel.md)

# Model setup

Implement the contract, use the trait, and spread twoFactorCasts() into your model’s casts:

```php
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:

```php
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 state
```

## State helpers

```php
$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:

```php
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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

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

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel.md)
