Laravel一覧

Laravel 5に2段階認証システムを導入する(Google2FA)

Laravel5系を使用していて、二段階認証をする機会というのがあると思います。

今回の記事では、Google Authenticatorを利用したLaravel5での二段階認証の方法について紹介します。

Laravelのインストールの方法については、下記の記事を参照ください。

LAMP環境やLNMP環境にPHPフレームワーク Laravel5.5・Laravel5.6をインストールする方法について紹介しています。 Laravel5.5・Laravel5.6をインストールする際の参考にしてください。

また、この記事は、Laravel5系でログイン認証ができていることが前提の記事です。Laravel5系を使用したログイン認証の方法については、下記の記事を参照ください。

Laravel5.5でログイン機能を実装して見ました。Laravel5では、ログイン機能はartisanコマンド1つで実装することが出来ます。Laravel5でログイン機能を実装しようと考えている方は、参考にしてください。

パッケージのインストール

Composerを使用してパッケージをインストールします2段階認証を実装してくれるGoogle2FAとQRコードを作成してくれるパッケージをインストールします。

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

Laravel 5.4以下はconfig/app.phpのprovidersとaliasesに追加してください。
providersに

PragmaRX\Google2FALaravel\ServiceProvider::class,

をaliasesの配列に

‘Google2FA’ => PragmaRX\Google2FALaravel\Facade::class,

を追加します。Laravel4系の場合は、app/config/app.phpに追加します。

最後に設定ファイルを公開します。

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

次に、RegisterControllerの先頭にリクエストクラスを含めます。

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

use Illuminate\Http\Request;

次にRegisterControllerに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']]);
    }

次に、QRコードを表示するviewを作成します。今回は、resources/viewsの中にgoogle2faディレクトリを作成し、register.blade.phpというファイルを作成します。

// 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>二段階認証をするためにバーコードをスキャンしてください。代わりにコードを使用することもできます。 {{ $secret }}</p>
                    <div>
                        <img src="{{ $QR_Image }}">
                    </div>
                    <p>続行する前にGoogle Authenticatorアプリを設定する必要があります。そうでなければログインすることができません。</p>
                    <div>
                        <a href="/complete-registration"><button class="btn-primary">Complete Registration</button></a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

今、会員登録の処理を行うと、QRコードが表示されるページに飛ばされるはずです。

ユーザーの登録

まだ、この時点では、二段階認証は完成していません。次にユーザー登録の処理をする必要があります。
まずそのためには、Google Authenticatorのシークレットキーを保存するカラムをusersテーブルに作成する必要があります。

そのためには、次のコマンドを打ちます。

php artisan make:migration add_google2fa_column_to_users --table=users

マイグレーションファイルが次のようにできるはずです。下記のように編集します。

// database/migrations/201X_XX_XX_XXXXXX_add_google2fa_column_to_users.php
<?php

     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            //下の行を追加
       $table->text('google2fa_secret');
        });
    }

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

このマイグレーションファイルは、Google Authenticatorのシークレットキーを保存するカラムをusersテーブルに作成してくれます。

マイグレーションをしてみましょう。

php artisan migrate

次は、登録のステップです。RegisterController.phpを以下のように編集します。

編集前

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

    use RegistersUsers;

を下記のように編集します。

編集後

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

    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;
    }

次にユーザー登録完了をroute.phpに設定します。

// routes/web.php 

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

次にcompleteRegistrationメソッドを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);
    }

デフォルトのlaravelでは、ユーザー登録の際登録されるのは、名前・email・パスワードだけです。
Google Authenticatorのシークレットキーが保存されるように、RegisterController.phpのcreateメソッドも変更します。

/ 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'],
        ]);
    }

次にuserモデルのを下記のように編集します。

// 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',
    ];

このままでも動きますが、セキュリティを強化するために、データベースが危険にさらされてもユーザーが危険にさらされないよう、Userモデルに変更加えgoogle2fa_secretを暗号化します。

// app/User.php

    /**
     * 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)
    {
        return decrypt($value);
    }

まだ、2段階認証の処理は完成していませんが、シームレスにログインできるようになっていればここまでの作業は成功です。

ログインの処理に2段階認証を追加する

ここまでの処理では、メールアドレスとパスワードの認証をしたのと変わりはありません。
次に2段階認証の処理を追加していきます。

一番簡単な方法は、app/Http/Kernel.phpのrouteMiddlewareに、pragmarx/google2fa-laravelのパッケージを追加することです。

// app/Http/Kernel.php

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

これにより、必要なときはいつでも2faを使用してミドルウェアを参照できます。

次に、ログイン後にユーザーがワンタイムパスワードを入力するビューを定義します。デフォルトでは、resources/views/google2fa/ index.blade.phpでビューを使用するように構成されています。そのビューを作成して以下を追加します。

// 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

routes/web.phpに以下のように設定します。

// routes/web.php

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

2faへの入力要求に応答して前のURLにリダイレクトする経路を作成しました。2faミドルウェアの後ろにルートを設定しているので、それがリクエストオブジェクトに含まれていればそれはワンタイムパスワードを検証します。

これで、ミドルウェアを使用して、それを必要とするアプリケーションのあらゆる側面を制限できます。

例えば、HomeControllerを次のように変更します。

変更前

// app/Http/Controllers/HomeController.php

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

次のように変更します。

変更後

// app/Http/Controllers/HomeController.php

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

そうすると、ユーザーがログインしてhomeへ行く前に、ワンタイムパスワードを求められます。

ワンタイムパスワードを入力すると、ログインできます。
Laravel5を使用したGoogle Authenticatorによる2要素認証の完成です。
今回の記事では、Google Authenticatorによる2要素認証をLaravelアプリケーションに追加しました。

セキュリティ保護の観点から2段階認証の必要なシステムというのも結構あるのだと思います。
Laravel5を使用したGoogle Authenticatorによる2要素認証を作成する際の参考にしてください。

[参考]・How to Add Google’s Two Factor Authentication to Laravel