Published October 11, 2020 • (8 min read)
A tutorial on how to implement a frontend build for a user to enable and disable two factor authentication with Laravel Fortify.
This tutorial will be starting from a fresh install of Laravel 8 with our database details setup and using Vuejs 2 for the frontend (though this can be adapted to work with a Javascript framework of your choice).
If you have already completed the installation of Laravel Fortify and have added it's auth views you can skip the set up section.
Alternatively, if you just want the completed version you can find the code here
Here we will go through the process of installing Fortify and setting up the login and home views
To get started, install Fortify using Composer:
composer require laravel/fortify
Next, publish Fortify's resources:
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
Add the new provider to your array of providers in config/app.php
.
'providers' => [
/*
* Application Service Providers...
*/
App\Providers\FortifyServiceProvider::class,
]
This will fix errors like the below when using Fortify's routes.
Target [Laravel\Fortify\Contracts\LoginViewResponse] is not instantiable.
Next, run the migrations:
php artisan migrate
If you haven't done already we can setup Vuejs:
npm install vue
and import it into our resources/js/app.js
file.
import Vue from 'vue'
window.app = new Vue({
el: '#app',
})
Now we can create the views. First let's build a layout, create app.blade.php
in the layouts folder.
<!DOCTYPE html>
<html>
<body>
<div id="app">
@yield('content')
</div>
<script src="{{ mix('js/app.js') }}"></script>
</body>
</html>
Next we can create the login and register views. Add auth
folder in your resources/views
and create a login.blade.php
file.
@extends('layouts.app')
@section('content')
<form method="POST">
@csrf
<label>{{ __('Email') }}</label>
<input type="text" name="email" />
<label>{{ __('Password') }}</label>
<input type="password" name="password" />
<button>
Submit
</button>
</form>
@endsection
Then create a register.blade.php
in the same folder.
@extends('layouts.app')
@section('content')
<form method="POST">
@csrf
<label>{{ __('Name') }}</label>
<input type="text" name="name" />
<label>{{ __('Email') }}</label>
<input type="text" name="email" />
<label>{{ __('Confirm Password') }}</label>
<input type="password" name="password" />
<label>{{ __('Confirm Password') }}</label>
<input type="password" name="password_confirmation" />
<button>
Submit
</button>
</form>
@endsection
We now need to tell Fortify to use these views as our new auth pages, we can do this by adding the following code to the boot
method in App\Providers\FortifyServiceProvider.php
Fortify::loginView(function () {
return view('auth.login');
});
Fortify::registerView(function () {
return view('auth.register');
});
Once a user is logged in they will be navigated to the signed in users home, the default is /home
but this can be changed in App\Providers\RouteServiceProvider.php
public const HOME = '/home';
For now we will keep it this way and create a route and view for this url.
First let's create the home view home.blade.php
@extends('layouts.app')
@section('content')
<h2>
You are signed in as {{ auth()->user()->name }}
</h2>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button>Logout</button>
</form>
@endsection
A simple view with a form allowing the use to logout.
Next, add the route to our routes/web.php
Route::middleware('auth')->get('home', function() {
return view('home');
});
Now let's set up the server:
php artisan serve
Now navigate to /register
or /login
if you already have a user and fill in the form. Once logged in you will now see the home template we created above.
To enable Fortify's two factor authentication feature we need to add it to our features array in config/fortify.php
.
This should be activated by default when Fortify was published but if not add it in.
'features' => [
Features::twoFactorAuthentication([
'confirmPassword' => true,
]),
]
The confirmPassword
argument makes it so the user has to re-enter their password in order to enable two factor authentication.
Next, add the two factor authenticatable trait to our User
model.
use Laravel\Fortify\TwoFactorAuthenticatable;
class User extends Authenticatable
{
use TwoFactorAuthenticatable;
}
then add the two factor fields to the hidden array of the model
protected $hidden = [
'two_factor_recovery_codes',
'two_factor_secret',
];
Fortify generates all the routes we need in order to set the user up with two factor authentication but it's up to us to add the views to call these.
The steps (routes) we need to take are:
/user/confirmed-password-status
)/user/confirm-password
)/user/two-factor-authentication
)/user/two-factor-qr-code
)First we need to create a component to check if the user has confirmed their password and if not present them with a prompt to do so.
Create the confirm password vue component in resources/js/components/ConfirmPassword.vue
<template>
<div>
<div v-if="confirmingPassword">
<form @submit.prevent="confirmPassword">
<input v-model="password" type="password" />
<button>
Confirm
</button>
</form>
</div>
<span v-else @click="startConfirmingPassword">
<slot />
</span>
</div>
</template>
<script>
export default {
data () {
return {
confirmingPassword: false,
password: ''
}
},
methods: {
startConfirmingPassword () {
axios.get('/user/confirmed-password-status')
.then((response) => {
if (response.data.confirmed) {
this.$emit('confirmed')
} else {
this.confirmingPassword = true
}
})
},
confirmPassword () {
axios.post('/user/confirm-password', {
password: this.password,
}).then(response => {
this.confirmingPassword = false
this.$emit('confirmed')
})
},
}
}
</script>
Create the two factor authentication vue component in resources/js/components/TwoFactorAuth.vue
and add the enable button in the slot of your newly created confirmPassword component
<template>
<div>
<h2>
Two Factor Authentication
</h2>
<confirm-password>
<button>
Enable
</button>
</confirm-password>
</div>
</template>
<script>
import ConfirmPassword from './ConfirmPassword'
export default {
components: {
ConfirmPassword
},
}
</script>
Now we can register our component in our resources/js/app.js
file
Vue.component('two-factor-auth', require('./components/TwoFactorAuth').default)
window.app = new Vue({
el: '#app',
})
and then add the component to our resources/views/home.blade.php
file inbetween the content section.
<two-factor-auth />
Build our vue files:
npm run watch
Navigate to the /home
page and we should see the enable button. Now when the user clicks the enable button they will be prompted to confirm their password if they already haven't.
Next, we need to add a call to enable two factor authentication on confirmation of password and then retrieve the QR code on a successful response. We can add two data properties, one to tell us whether two factor has been enabled and the other to store the QR code.
data () {
return {
twoFactorEnabled: false,
qrCode: ''
}
},
methods: {
enableTwoFactorAuthentication () {
axios.post('/user/two-factor-authentication')
.then(() => {
return Promise.all([
this.showQrCode()
])
}).then(() => {
this.twoFactorEnabled = true
})
},
showQrCode () {
return axios.get('/user/two-factor-qr-code')
.then(response => {
this.qrCode = response.data.svg
})
},
}
Note: If the confirmPassword argument has been set in the two factor authentication feature
/user/two-factor-qr-code
can only be called if the user has confirmed their password else a 500 error will be thrown.
We can call the enable method on confirmation by attaching it to the confirmed event. Once enabled we can hide the enable button with v-if
.
We can also add a contianer to show the retrieved QR code.
<div v-if="qrCode" v-html="qrCode" />
<confirm-password v-if="!twoFactorEnabled" @confirmed="enableTwoFactorAuthentication()">
<button>
Enable
</button>
</confirm-password>
Now our user can enable two factor authentication and can scan the returned QR code.
To disable two factor authentication we can add a similar button and method to the component. First let's add the method.
disableTwoFactorAuthentication () {
axios.delete('/user/two-factor-authentication')
.then(() => {
this.twoFactorEnabled = false
this.qrCode = ''
})
}
This will send a delete request and remove any two factor secrets and codes from the user.
We can add the disable button after the enable button to use Vue's if else conditional templating and call the disable method on the confirmed event
<confirm-password v-else @confirmed="disableTwoFactorAuthentication()">
<button>
Disable
</button>
</confirm-password>
and now the user can disable two factor authentication.
To make sure the disable button appears if we have two factor enabled and we reload the page we can pass an enabled property to the two factor component.
First let's add a check for two factor into the User
Model.
/**
* @return bool
*/
public function twoFactorAuthEnabled()
{
return !is_null($this->two_factor_secret);
}
Next, we need to allow the two factor component to accept the property. In resources/js/component/TwoFactorAuth.vue
add the props and modify the twoFactorEnabled
data variable to use it.
props: {
enabled: {
type: Boolean,
default: false
}
},
data () {
return {
twoFactorEnabled: this.enabled,
qrCode: ''
}
}
Now in our resources/views/home.blade.php
we can pass in the check for the authentication.
<two-factor-auth :enabled="{{ json_encode(auth()->user()->twoFactorAuthEnabled()) }}" />
Once two factor authentication has been enabled and the QR code scanned we can log out and follow the login process by navigating to /login
and filling in the form. Once submitted we will be taken to the two factor challenge page which we need to add the view for. Create resources/views/auth/two-factor-challenge.blade.php
and add the following code.
@extends('layouts.app')
@section('content')
<form method="POST" action="/two-factor-challenge">
@csrf
<label>{{ __('Code') }}</label>
<input type="text" name="code" />
<button>
Login
</button>
</form>
@endsection
Now we need to tell Fortify to use this view so let's add it to boot
method of app/Providers/FortifyServiceProvider.php
Fortify::twoFactorChallengeView(function() {
return view('auth.two-factor-challenge');
});
Reload the page, enter the code from your authentication app and hit submit. You will be taken to the user's home page, all done.
You have successfully set up two factor authentication with Laravel Fortify.