How To Add Google's Two Factor Authentication To A Laravel 8 Application.
Tags: laravel, 100daysofcode
Published: 2021-06-29 17:36:46

## Introduction

Laravel provides an amazing authentication scaffold that handles registration, login, and all other authentication parts for users that are easy to use.

However, the traditional email and password are becoming less secure due to many cyber attacks like SQL injections, phishing scams and data breaches. 

The concept of two-factor authentication(2FA) was created to overcome this shortcoming.

## What is Two Factor Authentication (2FA)?
Two-factor authentication (2FA) strengthens access security by requiring two methods to verify users identities.

The traditional password is already one factor of authentication which is something only the user should have. Some extra form of security that a user should also have includes biometrics(fingerprint), voice pattern recognition, or iris scan which are quite expensive but awesome. 

The second factor should be something that users don't readily have or aren't constant. One form of the second factor is One-Time Passwords(OTPs) which will be our focus here.


## What is a One-Time Password(OTP)?
A one-time password (OTP) is an automatically generated set of characters used to authorize a user for a specific action. *As the name implies, it can only be used once.* It can either be counter-based or time-based.

After the correct password is provided in the login form, the user is prompted for an OTP depending on your application or preference. This can be implemented in so many ways such as:

* Hardware Tokens
* One Time Password (OTP) sent via SMS
* Google Authenticator

In this guide, you will learn how to use Google Authenticator to implement Time based One-Time Password (TOTP) specified in [RFC 6238.](https://datatracker.ietf.org/doc/html/rfc6238) which uses HMAC-Based One-Time Password Algorithm (HOTP) specified in [RFC 4226](https://datatracker.ietf.org/doc/html/rfc4226) in building an authentication system.

We will be using [this package](https://github.com/antonioribeiro/google2fa) to implement the [Google Two-Factor Authentication](https://github.com/antonioribeiro/google2fa) on our Laravel Application.


> *Through this guide, I highlighted some issues, their solutions and modifications to help have a better experience with this packages.*

## Google Authentication Apps

To use the two-factor authentication, the user will have to install a Google Authenticator compatible app. Here are a few:
- [Google Authenticator for Android](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2)

- [Google Authenticator for iOS](https://apps.apple.com/us/app/google-authenticator/id388497605)


> *An advantage of using Google Authenticator is that after downloading the app to your smartphone, you can use it offline unlike other 2FA options which need to be connected to the internet to work which may be a disadvantage to users in a cut off location *for example, the basement of a building.*


## How Time based One-time Password works?

* After the user logs in successfully, a prompt showing a QR code and alternatively a code(set of characters to manually input if the user may not be able to scan QR code).

* Upon scanning or submitting the code, the server generates a secret key which is passed to the user.

* The secret key is combined with the current Unix timestamp to generate a six-digit code using a message authentication code (HMAC) based algorithm.

* The six-digit code is the OTP and it changes every 30 seconds.


__Now let's get to coding!😎__


## Building the Authentication System

###  Create the Authentication Scaffold.

 **Step 1: Create a Laravel application.**

You can use the laravel command or via the Composer package manager as follows:

```bash
laravel new project_name

or

composer create-project laravel/laravel project_name
```

**Step 2: Establish a database connection.**

 [Here](https://dev.to/roxie/how-to-connect-laravel-application-to-mysql-database-5han)  is a guide I wrote in [connecting your Laravel app to MySQL database](https://dev.to/roxie/how-to-connect-laravel-application-to-mysql-database-5han). If your database is different, do well to connect it appropriately.


**Step 3: Install Laravel/UI.**

```bash
composer require laravel/ui
```

**Step 4: Install Bootstrap Auth Scaffolding.**

The bootstrap authentication scaffold gives a UI and basic authentication for registration and login. You can install it with this `Artisan`  command:

```bash
php artisan ui bootstrap --auth
```

**Step 5: Install NPM Packages.**

```bash
npm install
```

**Step 6: Run NPM environment.**

```bash
npm run dev
```

**Step 7: Run the application.**

You can serve the laravel application with the following `Artisan Command`:
```bash
php artisan serve
```
**Step 8: Now hit this URL on your browser.**

```
http://localhost:8000/register
```

> _You should be able to view the register and login page like this_:

![Register](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u5p688ufjytzi2vsru7h.png)
![Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/82kcyl3cwr424lbt9b5x.png)





> __P.S: We haven't run the migrations yet so submitting the forms will return an error message.__




### Adding the Two-Factor Authentication to Registration.

#### AIM:
Let's pause and identify what we want to achieve:

- A user's secret for authenticator will be generated when a user tries to register
-  That secret will be used to show a QR Code for the user to set up their Google Authenticator upon the next request.
- The user is registered with their Google Authentication secret after submitting the correct OTP.


> __P.S: The QR code is accessible ONLY once for maximum security and if the user needs to set up 2FA again, they will have to repeat the process and invalidate the old one.__

### Section 1: Generating QR Code and Displaying secret

**Step 1: Install two packages.**
The first is  [Google Authenticator package for PHP](https://github.com/antonioribeiro/google2fa)  and the second package is a QR code generator which is  [BaconQrCode](https://github.com/Bacon/BaconQrCode).

```bash
composer require pragmarx/google2fa-laravel
composer require bacon/bacon-qr-code
```

**Step 2: Publish the config file.**
```bash
php artisan vendor:publish --provider="PragmaRX\Google2FALaravel\ServiceProvider"
```

**Step 3: Update `RegisterController` with `register()` method.**

> P.S: Include the `Request` class outside the controller class.

```php
use Illuminate\Http\Request;
```

```php
    public function register(Request $request)
      {
          //Validate the incoming request using the already included validator method
          $this->validator($request->all())->validate();
  
          // Initialise the 2FA class
          $google2fa = app('pragmarx.google2fa');
  
          // Save the registration data in an array
          $registration_data = $request->all();
  
          // Add the secret key to the registration data
          $registration_data["google2fa_secret"] = $google2fa->generateSecretKey();
  
          // Save the registration data to the user session for just the next request
          $request->session()->flash('registration_data', $registration_data);
  
          // Generate the QR image. This is the image the user will scan with their app
       // to set up two factor authentication
          $QR_Image = $google2fa->getQRCodeInline(
              config('app.name'),
              $registration_data['email'],
              $registration_data['google2fa_secret']
          );
  
          // Pass the QR barcode image to our view
          return view('google2fa.register', ['QR_Image' => $QR_Image, 'secret' => $registration_data['google2fa_secret']]);
      }
```

**Step 4: Override the default `register()` trait.**

Since we defined our own `register()` method, we need to update the trait to avoid a clash with the default `register()` method from the authentication scaffold.

Instead of 

```php
use RegistersUsers;
```
use this:
```php
  use RegistersUsers {
         // We are doing this so the predefined register method does not clash with the one we just defined.
           register as registration;
       }
``` 

**Step 5: Create the view to display the QR code.**

Our `register()` method already redirects to `view(google2fa.register.blade.php)`. This means we will create a`google2fa` folder and a `register.blade.php` file in it. The full path will be `resources/views/google2fa/register.blade.php`.

This will be the content of the file to display the QR code:
 
```php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Set up Google Authenticator</div>

                <div class="panel-body" style="text-align: center;">
                    <p>Set up your two factor authentication by scanning the barcode below. Alternatively, you can use the code {{ $secret }}</p>
                    <div>
                        <img src="{{ $QR_Image }}">
                    </div>
                    <p>You must set up your Google Authenticator app before continuing. You will be unable to login otherwise</p>
                    <div>
                        <a href="/complete-registration"><button class="btn-primary">Complete Registration</button></a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

> _P.S: Now after submitting valid credentials at registration, the user is redirected to a page with a valid QR code and also the secret(if they cant scan the code). However, users can't complete registration yet because we are yet to set up the controllers and route to handle that_.

![QR Code and Secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vd81pi8xs6djf70z1w3l.png)




### Section 2: Registering the user.

**Step 1 : Update User Migration.**
Since we haven't run our migrations yet, we can add a column for Google two factor authentication secret. We will update the `up()` method  in  `database/migrations/2014_10_12_000000_create_users_table.php` like this:

```php
  public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->text('google2fa_secret');
            $table->timestamps();
        });
    }

```

**Step 2: Run the migrations.**

We can now run our migrations to the database with this `Artisan` command:

```bash
php artisan run migrate
```
> **P.S: If you have run your migrations before now, Here is a  [guide](https://dev.to/roxie/adding-and-removing-columns-from-existing-tables-using-laravel-migrations-389g)  I wrote about  [Adding and Removing columns from  existing tables in database](https://dev.to/roxie/adding-and-removing-columns-from-existing-tables-using-laravel-migrations-389g) .** *(Fear not! I got you covered😎)*

**Step 3 : Update the `RegisterController` with `completeRegistration()` method.**

```php
 public function completeRegistration(Request $request)
      {        
          // add the session data back to the request input
          $request->merge(session('registration_data'));
  
          // Call the default laravel authentication
          return $this->registration($request);
      }
```
**Step 4: Update `User` model and `create()` method in `RegisterController` with `google2fa_secret` field.**

- First, we need to set the `User` model as `hidden` and `fillable` property so that it can be included and also hidden if we cast it to an array or JSON.

```php
protected $fillable = [
        'name',
        'email',
        'password',
        'google2fa_secret'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
        'google2fa_secret'
    ];
```

- Then we also need to update the `create()` method to accept the field like this:
```php
 protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'google2fa_secret' => $data['google2fa_secret'],
        ]);
    }
```

**Step 5: Set up routes for `complete-registeration`.**

This will be in our `routes/web.php` file like this:

```php 
Route::get('/complete-registration', [App\Http\Controllers\Auth\RegisterController::class, 'completeRegistration'])->name('complete-registration');
```
**Step 6 : Encrypt the `google2fa_secret`.**

We are all set😍, however, let's take an extra step for security and encrypt the `google2fa_secret` in the `User` model like this:

```php
 public function setGoogle2faSecretAttribute($value)
    {
         $this->attributes['google2fa_secret'] = encrypt($value);
    }

    public function getGoogle2faSecretAttribute($value)
    {
        return decrypt($value);
    }
```


> **Now a user that successfully registers with the correct OTP should see this:**

---
   __*HIGHLIGHT 1*__

 Here is an issue you're likely to face at this point depending on your Laravel version and its compatibility with these packages.

Here is one solution I figured out🤗:

The Bacon-Qr-code package seems to be most stable on version 1.0.3 so you can downgrade it with the following composer command:

```bash
composer require bacon/bacon-qr-code:~1.0.3
``` 
Now try to submit the form again, the registration should be successful!🎉👍

---


### Adding the Two-Factor Authentication to Log in.

Now we can generate the QR code as well as the secret, thereafter successfully register a user.

But we want to prompt users for their OTP before granting them access to any part of the app.🤔

**Step 1: Set up Route Middleware**

The `pragmarx/google2fa-laravel` package provides a middleware to prevent users from accessing the app unless OTP has been provided.

First, we need to add this to the `routeMiddleware` array in `app/Http/Kernel.php` before we can use it.

```php
 protected $routeMiddleware = [
        '2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
    ];
    
```
This will restrict any user that hasn't submitted a valid OTP from access to the home page. It will keep redirecting back to `resources/views/google2fa/index.blade.php` prompting for the OTP until a valid OTP is sent.

### USAGE
- After scanning the QR code or inputting Secret on the Google Authenticator app, it automatically generates an OTP on the Authenticator App.

- Click on Complete Registration then the user is prompted for the OTP and if OTP submitted is valid(being careful that the OTP on the app refreshes every 30 seconds, so the user must input the current OTP) then, the user is redirected to home page else user will keep being prompted for the OTP until the submission is valid. 

The OTP page which is `resources/views/google2fa/index.blade.php` looks like this:

![OTP page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxpq2fkci5uhrfvxiden.png)

- A user that has successfully logged in , validating both forms of authentication will see the home page:
![Successful Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ipajv13u1zuytkec884.png)


---
   __*HIGHLIGHT 2*__

I realized that it wasn't user-friendly to keep redirecting users  back to OTP page for a valid OTP if the submission was invalid. So I decided to return an error message when redirecting to inform users that the OTP entered was wrong. 

**Update the `resources/views/google2fa/index.blade.php` like this:**

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

@section('content')
<div class="container">
    <div class="row justify-content-center align-items-center " style="height: 70vh;S">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading font-weight-bold">Register</div>
                <hr>
                @if($errors->any())
                    <b style="color: red">{{$errors->first()}}</b>

                @endif

                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('2fa') }}">
                        {{ csrf_field() }}

                        <div class="form-group">
                            <p>Please enter the  <strong>OTP</strong> generated on your Authenticator App. <br> Ensure you submit the current one because it refreshes every 30 seconds.</p>
                            <label for="one_time_password" class="col-md-4 control-label">One Time Password</label>


                            <div class="col-md-6">
                                <input id="one_time_password" type="number" class="form-control" name="one_time_password" required autofocus>
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Login
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
```

Here is what the result will be if the submitted OTP is wrong:

 ![Wrong OTP](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ryfhchjnbdayedjt2l0r.png)

---

## Conclusion

Wow!🎉🤗 You have built an authentication system with Google's Two Factor Authentication💪💪.

Guess what😎? I made the entire code open-source [here](https://github.com/Roxie-32/google_2FA) on [Github](https://github.com/Roxie-32/google_2FA) 🤗. I am open to conversations, questions or contributions, especially concerning the highlights. I would love to know better ways to achieve these things.
You can drop your comments or reach out to me on [Twitter](https://twitter.com/OlasupoFunke).


Thanks for reading.🤝