Adding two-factor auth to Laravel 8 bootstrap livewire fortify project
Tags: laravel, bootstrap, livewire
Published: 2021-09-27 22:19:08

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

[Part 1](https://jasminetracey.com/laravel-8-with-bootstrap-livewire-and-fortify)

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](https://jasminetracey.com/updating-laravel-8-user-profile-information-using-bootstrap-livewire-and-fortify)

In this tutorial, We will cover:

- adding two-factor auth to your project

## Clone part one of our project
```bash
git clone --branch starter2 git@github.com:jasminetracey/lara8auth.git
cd lara8auth
```

Open your cloned project in IDE of choice I will be using [Visual Studio Code](https://visualstudio.microsoft.com/)

## Install [Alpine.js](https://alpinejs.dev/)

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

```bash
npm install --save alpinejs
```

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

```js
...

import Alpine from 'alpinejs';

window.Alpine = Alpine;

Alpine.start();

...
```

## Update Fortify Provider

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

```php
public function boot()
{
    ...

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

    ...
}
```

Create two-factor auth view
```bash
touch resources/views/auth/two-factor-challenge.blade.php
```

```html
@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
```

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

```php
...
use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasFactory, Notifiable, TwoFactorAuthenticatable;
...
```

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

```html
<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>
```

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.

```php
public $showQrCode = false;
public $showRecoveryCodes = false;

public function showRecoveryCodes()
{
    $this->showRecoveryCodes = true;
}
```

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

```php
public function getUserProperty()
{
    return Auth::user();
}
```

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.

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

    $this->showQrCode = true;
    $this->showRecoveryCodes = true;
}
```

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

```php
public function disableTwoFactorAuth(DisableTwoFactorAuthentication $disable)
{
    $disable(Auth::user());
}
```

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

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

    $this->showRecoveryCodes = true;
}
```

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

```html
@extends('layouts.app') 

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

        <livewire:password-change-form />

        <livewire:two-factor-form />
    </div>
@endsection
```

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](https://github.com/laravel/fortify) and for livewire documentation you can go to [livewire](https://laravel-livewire.com/)

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

{% github jasminetracey/lara8auth %}