🙄

Laravel BreezeとInertia.jsで二段階認証を実装してみる

2024/09/09に公開

Laravel BreezeとInertia.js(React/TypeScript)で二段階認証を実装してみる

この記事では、Laravel BreezeとInertia.js(React/TypeScript)を使って、二段階認証を含むアプリケーションを構築する手順を紹介します。BreezeはLaravelの軽量な認証パッケージで、非常にシンプルに認証システムをセットアップすることが可能です。

1. プロジェクトのセットアップ

まず、Laravelプロジェクトをセットアップします。

$ curl -s "https://laravel.build/two-factor-auth-demo" | bash
$ cd two-factor-auth-demo

Breezeのインストール

次に、Laravel Breezeをインストールし、ReactとTypeScriptを使用する設定を行います。

$ composer require laravel/breeze --dev
$ yarn install
$ php artisan breeze:install react --typescript
$ composer require inertiajs/inertia-laravel
$ ./vendor/bin/sail up -d
$ ./vendor/bin/sail artisan sail:publish

不要なDocker設定を削除し、mysqlの設定等を行ったら、環境を再構築します。

$ rm -rf docker/8.0 docker/8.1 docker/8.2 docker/mariadb docker/pgsql
$ ./vendor/bin/sail build --no-cache
$ ./vendor/bin/sail up -d

2. テーブルとマイグレーションの設定

二段階認証用にテーブルを追加するため、0001_01_01_000000_create_users_table.phpのマイグレーションファイルを編集します。

0001_01_01_000000_create_users_table.php
Schema::create('users', function (Blueprint $table) {
    ...
+    $table->string('two_factor_auth_code')->nullable();
+    $table->timestamp('two_factor_auth_code_expires_at')->nullable();
    ...
});
$ ./vendor/bin/sail artisan migrate

4. ミドルウェアの作成

次に、二段階認証を管理するためのミドルウェアを作成します。

$ ./vendor/bin/sail artisan make:middleware TwoFactorAuthenticateMiddleware

ミドルウェアの内容は、ユーザーがログインした後、二段階認証コードがあるかどうかを確認します。

TwoFactorAuthenticateMiddleware.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class TwoFactorAuthenticateMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        // 2段階認証コードがなければ、そのまま次の処理に進む
        if (!Auth::user()->two_factor_auth_code) return $next($request);

        return redirect()->route('two-factor.create');
    }
}

5. コントローラーとロジックの実装

二段階認証コードの送信と検証を行うため、TwoFactorAuthenticateControllerを作成します。

$ ./vendor/bin/sail artisan make:controller Auth/TwoFactorAuthenticateController

ユーザーがコードを入力する画面の表示や、コードの検証ロジックを実装します。

Auth/TwoFactorAuthenticateController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;

class TwoFactorAuthenticateController extends Controller
{
    /**
     * 2段階認証コードの入力画面を表示
     */
    public function create()
    {
        if (!Auth::user()->two_factor_auth_code) {
            return redirect(route('dashboard', absolute: false));
        }
        return Inertia::render('Auth/TwoFactorAuthenticate');
    }

    /**
     * 2段階認証コードの検証
     */
    public function store(Request $request)
    {
        $request->validate([
            'two_factor_auth_code' => 'required|string',
        ]);

        $now = Carbon::now();

        $user = User::find(Auth::id());
        $two_factor_auth_code = $user->two_factor_auth_code;
        $two_factor_expires_at = new Carbon($user->two_factor_auth_code_expires_at);

        // 2段階認証コードが一致し、有効期限内であれば、2段階認証を有効にする
        if ($request->two_factor_auth_code === $two_factor_auth_code && $now->lt($two_factor_expires_at)) {
            $user->two_factor_auth_code = null;
            $user->two_factor_auth_code_expires_at = null;
            $user->save();
            return redirect(route('dashboard', absolute: false));
        } else {
            return redirect(route('two-factor-authenticate.create', absolute: false));
        }
    }
}

6. ユーティリティクラスでメール送信処理を作成

二段階認証コードの生成とメール送信を行うUtils/TwoFactorAuthenticate.phpクラスを作成します。

Utils/TwoFactorAuthenticate.php
<?php

namespace App\Utils;

use Illuminate\Support\Facades\Mail;
use App\Mail\TwoFactorAuthenticateMail;
use App\Models\User;
use Carbon\Carbon;

class TwoFactorAuthenticate {
    /**
     * 2段階認証コードを生成してメール送信
     */
    public static function sendMail(User $user): void
    {
        // 2段階認証コードを生成
        $twoFactorAuthCode = random_int(100000, 999999);

        // 2段階認証コードをDBに保存
        $user->two_factor_auth_code = $twoFactorAuthCode;
        $user->two_factor_auth_code_expires_at = Carbon::now()->addMinutes(10);
        $user->save();

        // メール送信
        Mail::to($user->email)->send(new TwoFactorAuthenticateMail($twoFactorAuthCode));
    }
}

7. 認証と登録プロセスの調整

最後に、ユーザーがログインや新規登録時に二段階認証メールを送信するようにします。
use App\Utils\TwoFactorAuthenticate;を追加して、それぞれ下記メソッドに追記します。

Auth/AuthenticatedSessionController.php
    public function store(LoginRequest $request): RedirectResponse
    {
        $request->authenticate();

        $request->session()->regenerate();

        $user = Auth::user();

+        TwoFactorAuthenticate::sendMail($user);

        return redirect()->intended(route('dashboard', absolute: false));
    }

Auth/RegisterdUserController.php
    public function store(Request $request): RedirectResponse
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
            'password' => ['required', 'confirmed', Rules\Password::defaults()],
            'tel' => 'required|string|max:255',
        ]);

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

        event(new Registered($user));

        Auth::login($user);

+        TwoFactorAuthenticate::sendMail($user);

        return redirect(route('dashboard', absolute: false));
    }

8. ルーティングの調整

二段階認証を管理するためのミドルウェア(TwoFactorAuthenticateMiddleware)を/dashboard/logoutに追加します。

web.php
+use App\Http\Middleware\TwoFactorAuthenticateMiddleware;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

Route::get('/dashboard', function () {
    return Inertia::render('Dashboard');
})->middleware([
    'auth',
    'verified',
+    TwoFactorAuthenticateMiddleware::class
])->name('dashboard');

require __DIR__.'/auth.php';
auth.php
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\RegisteredUserController;
use App\Http\Controllers\Auth\TwoFactorAuthenticateController;
+use App\Http\Middleware\TwoFactorAuthenticateMiddleware;
use Illuminate\Support\Facades\Route;

Route::middleware('guest')->group(function () {
    Route::get('register', [RegisteredUserController::class, 'create'])
                ->name('register');

    Route::post('register', [RegisteredUserController::class, 'store']);

    Route::get('login', [AuthenticatedSessionController::class, 'create'])
                ->name('login');

    Route::post('login', [AuthenticatedSessionController::class, 'store']);

});

// 認証済みのユーザーのみアクセス可能
Route::middleware('auth')->group(function () {

    Route::get('two-factor', [TwoFactorAuthenticateController::class, 'create'])
                ->name('two-factor.create');

    Route::post('two-factor', [TwoFactorAuthenticateController::class, 'store'])
                ->name('two-factor.store');

    // 2段階認証済みのユーザーのみアクセス可能
+    Route::middleware([TwoFactorAuthenticateMiddleware::class])->group(function () {
        Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
                    ->name('logout');
    });
});

まとめ

この記事では、Laravel Breezeを利用した二段階認証の実装を紹介しました。
今回の記事では主にバックエンドの設定や基本的な認証機能の実装に焦点を当てていますが、フロントエンドのReactコンポーネントの具体的な実装や、メール送信のテンプレートのカスタマイズについては触れていません。これらの詳細な実装は、以下のGitHubリポジトリをご覧ください。
https://github.com/jr-p/two-factor-auth-demo

Discussion