DEV Community

Jasmine Tracey
Jasmine Tracey

Posted on • Originally published at jasminetracey.com

Adding two-factor auth to Laravel 8 bootstrap livewire fortify project

In part one of this article, we created our authentication setup using fortify, laravel and bootstrap.

Part 1

In part two of this article, we covered updating your profile information and how to change your password from your profile page using livewire

Part 2

In this tutorial, We will cover:

  • adding two-factor auth to your project

Clone part one of our project

git clone --branch starter2 git@github.com:jasminetracey/lara8auth.git
cd lara8auth
Enter fullscreen mode Exit fullscreen mode

Open your cloned project in IDE of choice I will be using Visual Studio Code

Install Alpine.js

We will be installing alpine.js to do some showing and hiding in the two factor form

npm install --save alpinejs
Enter fullscreen mode Exit fullscreen mode

Import package in the resources/js/app.js file

...

import Alpine from 'alpinejs';

window.Alpine = Alpine;

Alpine.start();

...
Enter fullscreen mode Exit fullscreen mode

Update Fortify Provider

To begin we will be updating our fortify provider to enable the 2factor page when the user logs in.

public function boot()
{
    ...

    Fortify::twoFactorChallengeView(function () {
        return view('auth.two-factor-challenge');
    });

    ...
}
Enter fullscreen mode Exit fullscreen mode

Create two-factor auth view

touch resources/views/auth/two-factor-challenge.blade.php
Enter fullscreen mode Exit fullscreen mode
@extends('layouts.app') 

@section('content')
<div class="container" x-data="{ recovery: false }">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Login') }}</div>

                <div class="card-body">
                    <div class="alert alert-info" role="alert" x-show="! recovery">
                        {{ __('Please confirm access to your account by entering the authentication
                        code provided by your authenticator application.') }}
                    </div>

                    <div class="alert alert-info" role="alert" x-show="recovery">
                        {{ __('Please confirm access to your account by entering one of your
                        emergency recovery codes.') }}
                    </div>

                    <form method="POST" action="{{ route('two-factor.login') }}">
                        @csrf

                        <div class="form-group row" x-show="! recovery">
                            <label for="code" class="col-md-4 col-form-label text-md-right"
                                >{{ __('Code') }}</label
                            >

                            <div class="col-md-6">
                                <input
                                    id="code"
                                    type="text"
                                    class="form-control @error('code') is-invalid @enderror"
                                    name="code"
                                    value="{{ old('code') }}"
                                    autocomplete="one-time-code"
                                />

                                @error('code')
                                <span class="invalid-feedback" role="alert">
                                    <strong>{{ $message }}</strong>
                                </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row" x-show="recovery">
                            <label for="recovery_code" class="col-md-4 col-form-label text-md-right"
                                >{{ __('Recovery Code') }}</label
                            >

                            <div class="col-md-6">
                                <input
                                    id="recovery_code"
                                    type="text"
                                    class="form-control @error('recovery_code') is-invalid @enderror"
                                    name="recovery_code"
                                    value="{{ old('recovery_code') }}"
                                    autocomplete="one-time-code"
                                />

                                @error('recovery_code')
                                <span class="invalid-feedback" role="alert">
                                    <strong>{{ $message }}</strong>
                                </span>
                                @enderror
                            </div>
                        </div>

                        <div class="flex items-center justify-end mt-4"></div>

                        <div class="mb-0 form-group row">
                            <div class="col-md-3 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Login') }}
                                </button>
                            </div>

                            <div class="col-md-5">
                                <button
                                    type="button"
                                    class="btn btn-secondary"
                                    x-show="! recovery"
                                    x-on:click="
                                                                    recovery = true;
                                                                    $nextTick(() => { $refs.recovery_code.focus() })
                                                                "
                                >
                                    {{ __('Use a recovery code') }}
                                </button>

                                <button
                                    type="button"
                                    class="btn btn-secondary"
                                    x-show="recovery"
                                    x-on:click="
                                                                    recovery = false;
                                                                    $nextTick(() => { $refs.code.focus() })
                                                                "
                                >
                                    {{ __('Use an authentication code') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
Enter fullscreen mode Exit fullscreen mode

Update User Model

We also need to tell our user model that we will be enabling 2factor authentication so we need to add a trait TwoFactorAuthenticatable

...
use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasFactory, Notifiable, TwoFactorAuthenticatable;
...
Enter fullscreen mode Exit fullscreen mode

Create livewire enable two-factor form

This form will be used to manage 2factor authentication. This includes enabling, disabling, and regenerating recovery codes.

From your terminal run the following command php artisan livewire:make two-factor-form This will create our livewire component.

In your resources/views/livewire/profile-form.blade.php add the following code to create our form fields.

<section class="my-5">
    @if (session('status') == 'two-factor-authentication-enabled')
    <div class="mb-4 text-sm font-medium text-green-600">
        Two factor authentication has been enabled.
    </div>
    @endif

    <div class="card">
        <div class="card-body">
            <h5 class="card-title">Two-Factor Authentication</h5>

            @if (!empty($this->user->two_factor_secret)) 

                @if ($showQrCode)
                    <p>
                        Two factor authentication is now enabled. Scan the following QR code using your
                        phone's authenticator application.
                    </p>

                    <div>{!! $this->user->twoFactorQrCodeSvg() !!}</div>
                @endif 

                @if ($showRecoveryCodes)
                    <div class="mt-4">
                        <p>
                            Store these recovery codes in a secure password manager. They can be used to
                            recover access to your account if your two factor authentication device is lost.
                        </p>

                        <div>
                            @foreach (json_decode(decrypt($this->user->two_factor_recovery_codes), true) as
                            $code)
                                <div>{{ $code }}</div>
                            @endforeach
                        </div>
                    </div>
                @endif

                <div class="mt-4">
                    @if ($showRecoveryCodes)
                        <button wire:click="regenerateRecoveryCodes" class="btn btn-secondary">
                            Regenerate Recovery Codes
                        </button>
                    @else
                        <button wire:click="showRecoveryCodes" class="btn btn-secondary">
                            Show Recovery Codes
                        </button>
                    @endif

                    <button wire:click="disableTwoFactorAuth" class="btn btn-primary">
                        Disable Two-Factor Authentication
                    </button>
                </div>

            @else
                <div>
                    <p>You have not enabled two factor authentication.</p>

                    <button wire:click="enableTwoFactorAuth" class="btn btn-primary">
                        Enable Two-Factor Authentication
                    </button>
                </div>
            @endif
        </div>
    </div>
</section>
Enter fullscreen mode Exit fullscreen mode

After adding our view we need to add the code in the livewire component file app\Http\Livewire\TwoFactorForm.php to interact with fields.

We will first add two fields to the component to hide and show QRCode and the RecoveryCode. And the function to toggle showing the recovery codes.

public $showQrCode = false;
public $showRecoveryCodes = false;

public function showRecoveryCodes()
{
    $this->showRecoveryCodes = true;
}
Enter fullscreen mode Exit fullscreen mode

Next we will include a user property to the component because we will be interacting with a couple fields on the authenticated user.

public function getUserProperty()
{
    return Auth::user();
}
Enter fullscreen mode Exit fullscreen mode

The first function we will be adding is the enableTwoFactorAuth function. This is the one that gets the ball rolling and allowing the user to setup two-factor authentication. We will be using the EnableTwoFactorAuthentication action that comes with fortify.

public function enableTwoFactorAuth(EnableTwoFactorAuthentication $enable)
{
    $enable(Auth::user());

    $this->showQrCode = true;
    $this->showRecoveryCodes = true;
}
Enter fullscreen mode Exit fullscreen mode

Remember to import the EnableTwoFactorAuthentication action

The next function will be the disableTwoFactorAuth function. If for some reason the user no longer wishes to have to factor authentication on their account we will provide the option to disable it. Again we will be using a fortify action DisableTwoFactorAuthentication.

public function disableTwoFactorAuth(DisableTwoFactorAuthentication $disable)
{
    $disable(Auth::user());
}
Enter fullscreen mode Exit fullscreen mode

Remember to import the DisableTwoFactorAuthentication action

Finally we will be adding a function to regenerate recovery codes. These recovery codes allow the user to authenticate if they lose access to their mobile device.

public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generate)
{
    $generate(Auth::user());

    $this->showRecoveryCodes = true;
}
Enter fullscreen mode Exit fullscreen mode

Remember to import the GenerateNewRecoveryCodes action

Finally, we will add our component to the resources/views/profile.blade.php page so that we can see the form when we visit the profile page.

@extends('layouts.app') 

@section('content')
    <div class="container">
        <livewire:profile-form />

        <livewire:password-change-form />

        <livewire:two-factor-form />
    </div>
@endsection
Enter fullscreen mode Exit fullscreen mode

Now when you go to /profile you should have the working 2factor form.

Conclusion

To find out more about laravel fortify features you can go the the github respository Fortify and for livewire documentation you can go to livewire

Thanks for reading please comment below and share if you found this article helpful.

GitHub logo jasminetracey / lara8auth

This is a simple auth starter setup for laravel 8 projects using bootstrap and laravel fortify

BOOTSTRAP 4 LARAVEL FORTIFY AUTHENTICATION

This is a simple auth starter setup for laravel 8 projects

Features

  • User Login
  • User Registration
  • Email Verification
  • Forget Password
  • Reset Password
  • Change Password
  • Update User Profile
  • TwoFactor Authentication
  • Browser Session Management

Discussion (2)

Collapse
bobbyiliev profile image
Bobby Iliev

This is a really great post! Well done!

Collapse
jasminetracey profile image
Jasmine Tracey Author

Thanks