Google's Two Factor Authentication with Laravel

Giới thiệu

Xác thực hai yếu tố (2FA), thường được gọi là Xác minh hai bước. Hiện nay việc login với Email và Password dần trở nên không còn bảo mật nữa và việc sử dụng Password đơn giản cùng với việc sử dụng cùng một password cho nhiều Site khác nhau. (2FA) cung cấp thêm một lớp bảo mật khiến cho kẻ tấn công khó truy cập vào thiết bị và tài khoản trực tuyến của người dùng vì biết mật khẩu của nạn nhân là một mình không đủ để vượt qua kiểm tra xác thực. Trong bài viết này, chúng ta sẽ tìm hiểu các bước để cài đặt 2FA (Two Factor Authentication) cho một ứng dụng Laravel một cách đơn giản.

Chuẩn bị

Tất nhiên bạn phải có project Laravel

Cài đặt

1. Cài đặt Laravel Authentication

Sử dụng Authentication default của Laravel

php artisan make:auth

# create the database tables needed with
php artisan migrate

Xem site chúng ta sẽ thấy LoginRegister ở đầu site

2. Thêm two factor authentication vào đăng ký

Mục dích của ứng dụng này:

  1. Khi đăng ký mới User sẽ sinh ra một mã bảo mật.
  2. Ở request tiếp theo chúng ta sẽ sử dụng mã đó để tạo ra QR code để user đăng ký với Google Authenticator của họ.
  3. Khi User click OK chúng ta sẽ tạo mới User cùng với mã bảo mật google Authenticator.

Trước tiên chúng ta cần cài đặt 2 packages sau

composer require pragmarx/google2fa-laravel
composer require bacon/bacon-qr-code 1.0.3

Nếu bạn đang sử dụng Laravel 5.4 trở xuống thì bạn cần thêm 2 dòng code bên dưới vào file config/app.php

'providers' => [
    //
    PragmaRX\Google2FALaravel\ServiceProvider::class,
    //
],

'aliases' => [
    //
    'Google2FA' => PragmaRX\Google2FALaravel\Facade::class,
    //
],

Tiếp theo ta cần publish cho file config 2FA

php artisan vendor:publish --provider=PragmaRX\\Google2FALaravel\\ServiceProvider

Tiếp theo chúng ta cần thay đổi flow đăng ký user default của Laravel

Chúng ta cần include request class vào RegisterController

// app/Http/Controllers/Auth/RegisterController.php

use Illuminate\Http\Request;

Sau đó define phương thức register

// app/Http/Controllers/Auth/RegisterController.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']]);
    }

Tạo file view để hiển thị QR code

// resources/views/google2fa/register.blade.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>
                    @if (!@$reauthenticating) {{-- add this line --}}
                        <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>
                    @endif {{-- and this line --}}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Sau khi đăng ký thông tin thì màn hình tiếp theo sẽ show QR code và mã SECRET như bên dưới.

Tiếp theo chúng ta thêm field google2fa_secret để lưu mã xác thực

php artisan make:migration add_google2fa_column_to_users --table=users
// database/migrations/201X_XX_XX_XXXXXX_add_google2fa_column_to_users.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddGoogle2faColumnToUsers extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->text('google2fa_secret')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('google2fa_secret');
        });
    }
}

Tiếp theo chúng ta run migration để thêm field vào table Users

php artisan migrate

Tiếp theo chúng ta phải override phương thức register trong RegisterController

// app/Http/Controllers/Auth/RegisterController.php

    use RegistersUsers;

    //Thay bằng 

    use RegistersUsers {
     // change the name of the name of the trait's method in this class
     // so it does not clash with our own register method
        register as registration;
    }

Tiếp theo chúng ta tạo route complete-registration để sau khi quét mã QR code xong sẽ gọi hàm register default của Laravel

// routes/web.php 

Route::get('/complete-registration', 'Auth\RegisterController@completeRegistration');

Sau đó ta sẽ tạo phương thức completeRegistration trong RegisterController

// app/Http/Controllers/Auth/RegisterController.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);
    }

Bởi vì register user default của Laravel chỉ lưu name, emailpassword ta thêm đoạn code bên dưới để lưu google2fa_secret

// app/Http/Controllers/Auth/RegisterController.php

    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
            'google2fa_secret' => $data['google2fa_secret'],
        ]);
    }
// app/User.php

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password', 'google2fa_secret',
    ];

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

    /**
     * Ecrypt the user's google_2fa secret.
     *
     * @param  string  $value
     * @return string
     */
    public function setGoogle2faSecretAttribute($value)
    {
        $this->attributes['google2fa_secret'] = encrypt($value);
    }

    /**
     * Decrypt the user's google_2fa secret.
     *
     * @param  string  $value
     * @return string
     */
    public function getGoogle2faSecretAttribute($value)
    {
        if (is_null($value))
        {
            return false;
        }
        return decrypt($value);
    }

Đến đây chúng ta có thể đăng ký mới User với 2FA rồi, mở site lên và test thôi nào.

3. Thêm two factor authentication vào Login

Tới phần quan trọng nhất rồi, đó là chúng ta thay đổi flow login default của Laravel với việc thêm vào bước xác thực One time password của 2FA. Múc đích là bắt buộc User phải nhập mã Google Authenticator trước khi access vào site.

Trước tiên chúng ta thêm Middleware vào app/Http/Kernel.php

// app/Http/Kernel.php

    protected $routeMiddleware = [
        ...
        '2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
    ];

Tiếp theo chúng ta cần một Page để người dùng nhập mã Google Authenticator vào

// resources/views/google2fa/index.blade.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">Register</div>

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

                        <div class="form-group">
                            <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

Tiếp theo chúng ta cần add router:

Route::post('/2fa', function () {
    return redirect('home');
})->name('2fa')->middleware('2fa');

Ở HomeController ta add middleware 2fa để yêu cầu xác thực

// app/Http/Controllers/HomeController.php

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    // Đổi thành

    public function __construct()
    {
        $this->middleware(['auth', '2fa']);
    }

Như vậy sau khi login bằng emailpassword khi user redirect đến /home thì họ bắt buộc nhập one-time password.

Ở đây chúng ta mở App Google Authenticator lên và nhập code vào là có thể login được rồi.

Kết thúc

Theo đánh giá của riêng mình thì bài viết cũng khá chi tiết rồi, các bạn có thể làm theo từng bước và kiểm tra kết quả nếu có vấn đề gì hoặc chưa hiểu chỗ nào thì có thể để lại Comment bên dưới mình sẽ Support nhé.Happy codding!

Posted in AWS, PHP, Vietnam Offshore on Feb 15, 2019