📱

Laravelに2要素認証を実装しよう

2023/05/05に公開

LaravelにBreezeをインストールするとログイン認証が行えますが、ログイン時のアカウントのセキュリティを強化するには、スマホでの2要素認証の方法がおススメです。
第三者が不正にアカウントにアクセスするのを防止し、アカウントを保護することができます。
実装するとログインする際に、通常のパスワードのログインの後に、本人のスマホで生成されたワンタイムパスワードによる追加の認証情報が必要になります。
今回は、Google Authenticatorアプリを使って、処理を実装したいと思います。
作成環境は、Windows11、xampp、php8.1、VSCode、Laravel10です。

Google Authenticator

iOSやAndroidのスマホ用のアプリケーションで、2段階認証のセキュリティコードを生成するために使用されます。
アプリをダウンロードしてアクセスすると、認証コードを生成します。
コードの有効期限は30秒で、時間を過ぎると新たなコードを生成します。
Google Authenticator

Laravel+Breezeのインストール

まずは、プロジェクトの作成。Larabelインストールの方法です。Versionが変わっても一緒です。
適当なプロジェクト名をつけて、インストールしたいディレクトリにインストールしてください。Windowsとxamppを使用してるので、powerShellでhtdocsまできて、このコマンドを入れます。

composer create-project --prefer-dist laravel/laravel project-name

//laravelのバージョンを指定するなら,例:バージョン8
composer create-project --prefer-dist laravel/laravel project-name "8.*"

インストールが終わったら、config/app.phpの設定を変更します。

config.php
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
'faker_locale' => 'ja_JP',

DataBaseの設定をします。
このプロジェクトで使用するデータベースを作成して、自身のデータベース情報をenvに入力してください。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=DataBase Name
DB_USERNAME=DataBase User
DB_PASSWORD=DataBase Password

次にUser認証のためのBreezeを入れます。
composerで読み込む。

composer require laravel/breeze --dev

終ったらインストール。私はblade派なのでblade.
React や Vue にも対応しています。
blade の部分をreact か vue に変えてください。

php artisan breeze:install blade

インストールが終わったら、データベースのマイグレーションをします。
もしusersテーブルに対して既存のカラムに変更を入れたいときは、先に変更してください。
database > migrations のなかにテーブル情報があります。
*後からでもできますが、どういうテーブルができるのか先に確認してください。

php artisan migrate

データベーステーブルもできたのでnpm install & run devします。

npm intall
npm run dev

これでサーバーを立ち上げたらLaravel のインストールは終わりです。

php artisan serve

google2fa インストール

vscodeのターミナルで次のコマンドを入力します。
laravel用のgoogle2faです。

composer require pragmarx/google2fa-laravel

少し時間がかかります。終ったら次を入力します。
qrコードを生成してくれます。

composer require bacon/bacon-qr-code

設定ファイルをpublishしてconfigに作成します。

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

これでconfigフォルダの中にgoogle2fa.phpができています。
google2faに関する細かい設定・情報が記載されています。

kernelのmiddlewareに2faで呼び出せるように追加します。
app > Http > Kernel.php

Kernel.php
    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        ...
        '2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
    ];

Migrationする

認証コードの判定用に使用する専用のカラムがデータベースに必要なので、Userテーブルにカラムを追加します。

php artisan make:migration add_goole_2fa_columns_to_users_table --table=users

できたmigrationファイルを編集します。
google2fa_secretは認証コード

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

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

修正したらターミナルで実行します。

php artisan migrate

Userモデルの修正

usersテーブルにカラムを追加したので、Userモデルを修正します。
app > Models > User.php

User.php
<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Database\Eloquent\Casts\Attribute;//追加

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

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

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
        'google2fa_secret'//追加
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * ここから追加
     * Interact with the user's first name.
     *
     * @param  string  $value
     * @return \Illuminate\Database\Eloquent\Casts\Attribute
     */
    protected function google2faSecret(): Attribute
    {
        return new Attribute(
            get: fn ($value) =>  decrypt($value),
            set: fn ($value) =>  encrypt($value),
        );
    }
}

Routeを設定

ログインの処理が変わるので、Google2fa用の routes を設定します。
dashoboardを開くには、middlewareの2faの処理を通らないといけなくなります。

app > routes > auth.php

auth.php
use App\Http\Controllers\ProfileController;//追加
use App\Http\Controllers\HomeController;//追加

Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');

    Route::middleware(['2fa', 'verified'])->group(function () {
        Route::get('/dashboard', [HomeController::class, 'index'])->name('dashboard');
        Route::post('/2fa', function () {
            return redirect(route('dashboard'));
        })->name('2fa');
    });
});

User登録時の処理

user の新規登録の際は、本人識別用のコードをあらかじめ登録しておく必要があります。
RegisterController の登録処理にgoogle2faの識別コードを追加します。

app > Http > Controllers > Auth > RegisterController.php

RegisterController.php
        $google2fa = app('pragmarx.google2fa');

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
            'google2fa_secret' => $google2fa->generateSecretKey(),
        ]);

ログイン時に認証コードを入力する画面を作る

viewsの中に、メールアドレスとパスワードで認証した後、アプリでのワンタイムパスワードを入力する画面を作成します。
Viewsの中に専用のbladeファイルを作成します。
app > resources > views > google2fa > index.blade.php
下記では、あらかじめ、publicフォルダの中にimagesをつくり、アプリのダウンロード用のアイコンを入れてあります。

index.blade.php
<x-guest-layout>
        <!-- Session Status -->
        <x-auth-session-status class="mb-4" :status="session('status')" />

        <!-- Validation Errors -->
        @if($errors->any())
        <div class="w-full mb-4">
            <div class="alert alert-danger">
                <strong>{{$errors->first()}}</strong>
            </div>
        </div>
        @endif

        @php
            $qrCodeUrl = Google2FA::getQRCodeInline(
                config('app.name'),
                Auth::user()->email,
                Auth::user()->google2fa_secret,
            );
        @endphp
        <div>{!! $qrCodeUrl !!}</div>

        <p class="mt-8">2要素認証には、スマホのGoogle Authenticatorアプリが必要です。以下からあなたのデバイスに合わせてインストールしてください。</p>
        <div class="flex items-center gap-4 pb-4">
            <div class="w-40">
                <a
                    href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=ja&gl=US"><img
                        src="{{ asset('images/icons/google-play-badge.png') }}"
                        alt=""></a>
            </div>
            <div class="">
                <a href="https://apps.apple.com/jp/app/google-authenticator/id388497605"><img
                        src="{{ asset('images/icons/Download_on_the_App_Store_Badge_JP_RGB_blk_100317.svg') }}"
                        alt="" class="w-36"></a>
            </div>
        </div>

        <form method="POST" action="{{ route('2fa') }}">
            @csrf
            <div class="my-6">
                <p class="text-sm mb-2">アプリに表示されている文字列を入力してください。30秒ごとに変わります。</p>
                <label for="one_time_password" value="__('ワンタイムパスワード')" />
                <input type="password" id="one_time_password" name="one_time_password"
                    class="block w-full rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
            </div>

            <div class="flex items-center justify-between mt-4">
                <div class="relative my-4">
                    <button type="submit"
                        class="w-full mx-auto text-white bg-sky-500 border-0 py-2 px-8 focus:outline-none hover:bg-sky-600 rounded text-lg tracking-wider">ログイン</button>
                </div>
            </div>
        </form>

</x-guest-layout>

QRコードを、スマホにインストールしたGoogleアプリで読み込みます。
アプリを立ち上げ、右下の+ボタンを押します。
QRコードをスキャンとでるので押し、カメラで読み込みます。
するとサイト名(メールアドレス)という表示の下に、ワンタイムパスワードが表示されるので、それをフォームに入力します。

通常のログイン画面→成功→QRコードのあるパスワード画面に移行します。
ここで成功するとダッシュボード画面に移行します。

簡単に実装できるので興味のある方はお試しください。
venderの中にpragmarxというフォルダができています。
そのなかのファイルを見ると動きが分かるので、configの設定ファイルと合わせて、カスタマイズも難しくないと思います。
2要素認証の使用の可否をユーザーに決めてもらい、ログイン時に使いたい人だけ使うといった使い方もできます。

参考:pragmarx/google2fa-laravel

Discussion