擬似的 WP Login
Auth ミドルウェアの基礎
問題
以下のような、擬似的な wp-login アプリを作成してください。
/wp-loginページに GET アクセス:- ログインしているユーザー:
/wp-adminにリダイレクト - ログインしていないユーザー:
ログイン画面を表示
- ログインしているユーザー:
/wp-loginページに POST アクセス:email => test@example.com, password => passwordを送信するとログイン状態になり、/wp-adminにリダイレクト- 認証に失敗した場合は、
wp-loginを再度表示
/wp-adminページに GET アクセス:- ログインしているユーザー:
ダッシュボード画面を表示 - ログインしていないユーザー:
/wp-loginにリダイレクト
- ログインしているユーザー:
/wp-logoutページに POST アクセス:- ログアウトされ、
wp-loginにリダイレクト
- ログアウトされ、
問題は、以下の手順で解いてください。
- Red:小さいテストを作成し、失敗を確認してください
- Green:テストを成功させてください
- Refactor:整理・整頓してください
- 必要に応じて、1から3を繰り返してください
ヒント
背景知識
- 認証: 公式ドキュメント
- リクエストライフサイクル: 或るリクエストの一生
- ミドルウェア: ミドルウェアの玉ねぎ構造
- テスト中でログインする方法: actingAs
便利なアサーションの例
アサーションの調べ方 も合わせてご覧ください。
今回は、以下を使うのではないかと思います。
-
assertAuthenticated($guard = null)(HTTP テスト : $guard で認証されているか)
-
assertGuest($guard = null)(HTTP テスト : $guard で認証されていないか)
-
assertRedirect($uri)(HTTP テスト : $uri にリダイレクトされたか)
-
assertSeeText($value)(HTTP テスト : テキストコンテンツに $value が含まれるか)
解答例
続きを読む
実行環境:
- Laravel v13.9.0
- PHP 8.4
- PHPUnit
以下の順にテストを行います:
/wp-adminのログイン・ログアウト時の動作確認/wp-loginの動作確認/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
特にありません。
解説
続きを読む
Laravel の Feature テストにおいては、 CSRF チェックが不要になります。
このため、テスト中に考えることが1つ減りますが、実際に web アプリを作成するときには、気をつけてください。
今回、リファクタリングした通り、メイン処理でない認証処理は、基本的にコントローラー内で処理すべきではありません。
ミドルウェアに移すべきです。
Laravel がデフォルトで対応しているミドルウェアがいくつかあり、そのうちの Auth ミドルウェア及び、 Guest ミドルウェアを、この解答例では利用しました。
Auth ミドルウェア
基本的な使い方は、回答例の通り、ルーティングファイルでミドルウェアを設定します。
ログインしていないユーザーがアクセスした場合、デフォルトでは login named route にリダイレクトします。
このため、必ず ->name('login') となるリダイレクト先を用意しておく必要があります。
named route は、内部的な名前なので、/login という URL にする必要はありません。
自由に決められるので、今回の問題のように、 /wp-login を login named routte にしても、全く問題ないというわけです。
一方、未ログインユーザーには 403 や、 404 コードを返したい場合もあるかもしれません。
Laravel 13 では、 redirectGuestsTo() を調べてみてください。
メソッドの引数コーラブル内で abort(403) などを呼び出せば良いようです。
ただし、エラーが返されたときの対応方法は、バージョンによって多少違いがあるようです。
詳細は、お使いのバージョンごとにドキュメントを調べてください。
Guest ミドルウェア
簡単に言えば、Auth ミドルウェアの逆です。
ログインしているユーザーがアクセスした場合は、 home もしくは dashboard named route にリダイレクトします。
リダイレクト先を編集したい場合は、 redirectUsersTo() を利用してください。