Laravel Magazine
Add Two-Factor Authentication to Laravel with Fortify

Add Two-Factor Authentication to Laravel with Fortify

Eric Van Johnson ·

Two-factor authentication (2FA) is one of the highest-impact security features you can add to an app, and Laravel Fortify gives you a backend implementation for free. Fortify is a headless authentication backend: it registers the routes and logic but leaves the views to you, which makes it a great fit whether you use Blade, Livewire, or a JavaScript frontend. This tutorial adds time-based one-time password (TOTP) 2FA, the kind you scan into Google Authenticator or 1Password, to an app for the fictional PHP Architect portal.

Step 1: Install Fortify

composer require laravel/fortify
php artisan fortify:install
php artisan migrate

The install command publishes Fortify's configuration and service provider. The migration adds the columns Fortify needs on your users table: two_factor_secret, two_factor_recovery_codes, and two_factor_confirmed_at.

Step 2: Enable the Feature

Open config/fortify.php and make sure the two-factor feature is enabled. Requiring password confirmation and a confirmation step is strongly recommended.

use Laravel\Fortify\Features;

'features' => [
    Features::twoFactorAuthentication([
        'confirm' => true,
        'confirmPassword' => true,
    ]),
],

The confirm option means a freshly generated secret is not considered active until the user proves they can produce a valid code, which prevents users from locking themselves out with a misconfigured authenticator app.

Step 3: Add the Trait to Your User Model

Fortify's two-factor logic lives in a trait. Add it to App\Models\User:

use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable
{
    use TwoFactorAuthenticatable;
}

Step 4: The Routes Fortify Gives You

With the feature enabled, Fortify registers the endpoints your UI will call. You do not write these controllers, you just call them from your settings page:

  • POST /user/two-factor-authentication enables 2FA and generates the secret.
  • POST /user/confirmed-two-factor-authentication confirms it with a code from the authenticator app.
  • GET /user/two-factor-qr-code returns the QR code SVG to display.
  • GET /user/two-factor-recovery-codes returns the recovery codes.
  • DELETE /user/two-factor-authentication disables 2FA.

Step 5: Build the Enable Flow

On your security settings page, a button posts to the enable route. Because we required password confirmation, the user will be prompted for their password first. After enabling, show the QR code and the recovery codes.

<form method="POST" action="/user/two-factor-authentication">
    @csrf
    <button type="submit">Enable Two-Factor Authentication</button>
</form>

@if (auth()->user()->two_factor_secret)
    <div class="qr">
        {!! auth()->user()->twoFactorQrCodeSvg() !!}
    </div>

    <p>Scan the code above, then enter a generated code to confirm:</p>

    <form method="POST" action="/user/confirmed-two-factor-authentication">
        @csrf
        <input name="code" inputmode="numeric" autocomplete="one-time-code">
        <button type="submit">Confirm</button>
    </form>
@endif

Always show the recovery codes at least once and tell users to store them safely. They are the only way back in if the device with the authenticator app is lost.

<ul>
    @foreach (json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true) as $code)
        <li>{{ $code }}</li>
    @endforeach
</ul>

Step 6: The Login Challenge

Once 2FA is confirmed, Fortify automatically intercepts the login. After a valid email and password, instead of logging the user straight in, it redirects to a two-factor challenge and stores the pending login in the session. You provide a view at the route Fortify redirects to, with a form that posts the code to /two-factor-challenge:

<form method="POST" action="/two-factor-challenge">
    @csrf
    <label>Authentication Code</label>
    <input name="code" inputmode="numeric" autocomplete="one-time-code">

    <label>Or use a recovery code</label>
    <input name="recovery_code">

    <button type="submit">Log in</button>
</form>

Fortify accepts either the six-digit TOTP code or one of the recovery codes, and each recovery code can only be used once.

Wrapping Up

That is the whole flow: install Fortify, enable the feature, add the trait, and build a few small views around the routes it provides. The cryptography, code generation, recovery codes, and login interception are all handled for you. If you would rather not build the views by hand, the Laravel starter kits ship with these screens already implemented on top of Fortify, which is a good reference even if you customize them later.

Further Reading

Stay Updated

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.