Laravel 演習入門

擬似的 WP Login

Auth ミドルウェアの基礎

問題

以下のような、擬似的な wp-login アプリを作成してください。


問題は、以下の手順で解いてください。

  1. Red:小さいテストを作成し、失敗を確認してください
  2. Green:テストを成功させてください
  3. Refactor:整理・整頓してください
  4. 必要に応じて、1から3を繰り返してください

ヒント

背景知識
便利なアサーションの例

アサーションの調べ方 も合わせてご覧ください。
今回は、以下を使うのではないかと思います。

解答例

続きを読む

実行環境:

以下の順にテストを行います:

  1. /wp-admin のログイン・ログアウト時の動作確認
  2. /wp-login の動作確認
  3. /wp-logout の動作確認

Red1: /wp-admin のログイン・ログアウト時の動作確認

テストを作成します。
bash:

php artisan make:test WpLoginTest

tests/Feature/WpLoginTest.php:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\User;

class WpLoginTest extends TestCase
{
    use RefreshDatabase;
    public function test_wp_adminにログアウト状態でアクセス(): void
    {
        $response = $this->get('/wp-admin');
        $response->assertRedirect('/wp-login');
    }
    public function test_wp_adminにログイン状態でアクセス(): void
    {
        $this->seed();
        $user = User::where('email', 'test@example.com')->first();
        $response = $this->actingAs($user)->get('/wp-admin');
        $response->assertStatus(200)
            ->assertSeeText('ダッシュボード');
    }
}

失敗を確認します。

Green1

初期ユーザーとして test@example.com が必要です。
私の利用したバージョンでは、最初から作成されました。
database/seeders/DatabaseSeeder.php:

    public function run(): void
    {
        User::factory()->create([
            'name' => 'Test User',
            'email' => 'test@example.com',
        ]);
    }

database/factories/UserFactory.php:

    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }

上記のファクトリー、シーダーの設定があることにより、 php artisan migrate:fresh --seed を実行するだけで、初期ユーザー Test User が作成されます。
テストにおいては、 use RefreshDatabase; および $this->seed(); が必要です。

ルーティングを設定します。
routes/web.php:

use App\Http\Controllers\WpLoginController;
Route::get('/wp-admin', [WpLoginController::class, 'show']);
Route::get('/wp-login', fn()=>'今の段階では何を返しても良い');

コントローラーを作成します。
bash:

php artisan make:controller WpLoginController

app/Http/Controllers/WpLoginController.php:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class WpLoginController extends Controller
{
    public function show(Request $request)
    {
        if(Auth::check()) {
            return 'ダッシュボード画面';
        }else{
            return redirect('/wp-login');
        }
    }
}

テストの成功を確認します。

Refactor1

認証処理は Auth ミドルウェアに任せます。
routes/web.php:

Route::middleware(['auth'])->group(function () {
    Route::get('/wp-admin', [WpLoginController::class, 'show']);
});
Route::get('/wp-login', fn()=>'今の段階では何を返しても良い')->name('login');

app/Http/Controllers/WpLoginController.php:

    public function show()
    {
        return 'ダッシュボード画面';
    }

Red2: /wp-login の動作確認

テストを追加します。
tests/Feature/WpLoginTest.php:

    public function test_wp_loginにログアウト状態でgetアクセス(): void
    {
        $response = $this->get('/wp-login');
        $response->assertStatus(200)
            ->assertSeeText('ログイン画面');
    }
    public function test_wp_loginにログイン状態でgetアクセス(): void
    {
        $this->seed();
        $user = User::where('email', 'test@example.com')->first();
        $response = $this->actingAs($user)->get('/wp-login');
        $response->assertRedirect('/wp-admin');
    }
    public function test_wp_login成功(): void
    {
        $this->seed();
        $response = $this->post('/wp-login', [
            'email' => 'test@example.com',
            'password' => 'password'
        ]);
        $response->assertRedirect('/wp-admin');
        $this->assertAuthenticated();
    }
    public function test_wp_login失敗(): void
    {
        $this->seed();
        $response = $this->from('/wp-login')->post('/wp-login', [
            'email' => 'test@example.com',
            'password' => 'error'
        ]);
        $response->assertRedirect('/wp-login');
        $this->assertGuest();
    }

テストの失敗を確認します。

Green2

ルーティングを編集します。
routes/web.php:

Route::get('/wp-login', [WpLoginController::class, 'get_login'])->name('login');
Route::post('/wp-login', [WpLoginController::class, 'post_login']);

コントローラーを編集します。
app/Http/Controllers/WpLoginController.php:

    public function get_login(Request $request)
    {
        if(Auth::check()) {
            return redirect('/wp-admin');
        }else{
            return 'ログイン画面';
        }
    }
    public function post_login(Request $request)
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);
 
        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();
 
            return redirect('/wp-admin');
        }else{
            return back();
        }
    }

テストを実行して、成功を確認します。

Refactor2

本来、メイン処理でやるべきでない認証処理は、 guest ミドルウェアに渡します。
routes/web.php:

Route::middleware(['auth'])->group(function () {
    Route::get('/wp-admin', [WpLoginController::class, 'show'])->name('dashboard');
});
Route::get('/wp-login', [WpLoginController::class, 'get_login'])->middleware(['guest'])->name('login');

app/Http/Controllers/WpLoginController.php:

    public function get_login(Request $request)
    {
        return 'ログイン画面';
    }

Red3: /wp-logout の動作確認

テストを追加します。
tests/Feature/WpLoginTest.php:

    public function test_wp_logoutでログアウト(): void
    {
        $this->seed();
        $user = User::where('email', 'test@example.com')->first();
        $response = $this->actingAs($user)->post('/wp-logout');
        $response->assertRedirect('/wp-login');
        $this->assertGuest();
    }

Green3

ルーティングを編集します。
routes/web.php:

Route::post('/wp-logout', [WpLoginController::class, 'logout']);

コントローラーを編集します。
app/Http/Controllers/WpLoginController.php:

    public function logout(Request $request)
    {
        Auth::logout();

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

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

        return redirect('/wp-login');
    }

Refactor3

特にありません。

解説

続きを読む

Note

Laravel の Feature テストにおいては、 CSRF チェックが不要になります。
このため、テスト中に考えることが1つ減りますが、実際に web アプリを作成するときには、気をつけてください。

今回、リファクタリングした通り、メイン処理でない認証処理は、基本的にコントローラー内で処理すべきではありません。
ミドルウェアに移すべきです。

Laravel がデフォルトで対応しているミドルウェアがいくつかあり、そのうちの Auth ミドルウェア及び、 Guest ミドルウェアを、この解答例では利用しました。

Auth ミドルウェア

基本的な使い方は、回答例の通り、ルーティングファイルでミドルウェアを設定します。

ログインしていないユーザーがアクセスした場合、デフォルトでは login named route にリダイレクトします。
このため、必ず ->name('login') となるリダイレクト先を用意しておく必要があります。

named route は、内部的な名前なので、/login という URL にする必要はありません。
自由に決められるので、今回の問題のように、 /wp-loginlogin named routte にしても、全く問題ないというわけです。

一方、未ログインユーザーには 403 や、 404 コードを返したい場合もあるかもしれません。
Laravel 13 では、 redirectGuestsTo() を調べてみてください。
メソッドの引数コーラブル内で abort(403) などを呼び出せば良いようです。

ただし、エラーが返されたときの対応方法は、バージョンによって多少違いがあるようです。
詳細は、お使いのバージョンごとにドキュメントを調べてください。

Guest ミドルウェア

簡単に言えば、Auth ミドルウェアの逆です。

ログインしているユーザーがアクセスした場合は、 home もしくは dashboard named route にリダイレクトします。

リダイレクト先を編集したい場合は、 redirectUsersTo() を利用してください。


<= 問題を読んだ・解いた・理解したなどのチェックにご利用ください。クリックすると、チェックが変化します。
問題一覧に戻る