エンジニア初心者がPHP/Laravelを学ぶ 仕事

代表的なログイン機能の実装方法

ログイン画面を作るときに、「POST送信先をどこにするのがいいか?」って後輩に聞かれ説明したので、その内容を記事にしたいと思う。

Post Back & Redirectとは

まず、結論としてログイン時のPOST送信先はログイン画面(login.asp)から、自分自身(login.asp)と伝えたんだけど、考えてみると確かに直感的にはログイン後に遷移したいページ(たとえば mypage.asp)に直接POSTするのが自然に思えるし、昔のサンプルコードなんかを見ると、そういう作り方も多かったりする。

でも、実際のWeb開発現場では、Post Back & Redirect というやり方が主流になってる。

この方法の基本的な流れはこう:

①ログイン画面(login.asp)から、自分自身(login.asp)にPOST送信する
②サーバー側でログイン処理(認証チェックなど)を実
③ログイン成功時には、HTTP 302などのステータスでmypage.aspへリダイレクト
④失敗した場合は、ログイン画面をそのまま表示してエラーメッセージを出す

一見遠回りなように見えるこの方法がなぜよく使われるのかというと、いくつか理由がある。

なぜPost Back & Redirect

まず、ブラウザの挙動が安定するという点が大きい。たとえばログインした直後にリロードしたり、戻るボタンを押したりしても、同じフォームが二重送信されたり、予期しない再POSTが起きたりといったトラブルを避けられる。

次に、画面とURLの関係が分かりやすくなる。たとえばログイン画面はlogin.asp、マイページはmypage.aspのように、役割ごとにURLを分けられる。処理の責任範囲もはっきりするし、保守もしやすい。

さらに、エラー処理がシンプルにできるっていうメリットもある。ログイン失敗時にはそのままlogin.aspで「ユーザー名またはパスワードが違います」といったメッセージを表示するだけ。わざわざ別の画面に遷移させる必要がないし、フォームの再表示もスムーズに行える。

直接遷移(少数派)

login.asp → (POST) → mypage.asp

この方式では、ログイン画面から直接mypage.aspにPOST送信して、認証処理と画面表示を両方やってしまう。
でもこのやり方は、ログイン失敗時のエラー処理がややこしい。エラー内容をどこに表示するのか、ログイン画面にどう戻すか、といった部分がごちゃごちゃしやすい。

Post Back & Redirect(多数派)

こっちは、一度自分自身にPOSTしてから処理を行い、成功したらリダイレクトでmypage.aspに遷移するやり方。エラーがあればそのまま同じ画面で表示するだけだから、シンプルだし扱いやすい。

技術的な仕組みとしては、「POST → 処理 → リダイレクト(GET)」という流れ。
サーバー側ではPOSTされた内容を受け取ってログイン処理を行い、成功したらHTTPレスポンスヘッダーに Location: mypage.asp を指定することでリダイレクトさせる。このとき、ブラウザはそのURLに対して自動的にGETリクエストを送る。つまり、POSTで送った処理の結果が、そのままGETリクエストに変わって次の画面に進む形になる。

Post Back & Redirectの実装

<form method="POST" action="{{ route('login.attempt') }}">
    @csrf

    <input type="email" name="email" placeholder="Email" value="{{ old('email') }}" />
    <input type="password" name="password" placeholder="Password" />
    <button type="submit">Submit</button>

    @if($errors->any())
        <ul>
            @foreach($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    @endif
</form>

コマンドでコントローラーを作成

php artisan make:controller AuthController
namespace App\Http\Controllers;

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

class LoginController extends Controller
{
    public function __invoke(Request $request): RedirectResponse
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();

            return redirect()->intended('dashboard');
        }

        return back()->withErrors([
            'email' => 'The provided credentials do not match our records.',
        ])->onlyInput('email');
    }
}

ルート(routes/web.php)を設定

use App\Http\Controllers\LoginController;

Route::get('/login', function () {
    return view('login');
})->name('login');

Route::post('/login', LoginController::class)->name('login.attempt');

Route::get('/dashboard', function () {
    return 'ようこそ、ダッシュボードへ!';
})->middleware('auth')->name('dashboard');

Inertia.js と Vue を使う場合

コマンドでコントローラーを作成

php artisan make:controller AuthController
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    // ログイン画面を表示
    public function create()
    {
        return inertia('Auth/Login'); // Inertia.js を使って画面表示
    }

    // ログイン処理
    public function store(Request $request)
    {
        $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string',
        ]);

        // 認証試行
        if (!Auth::attempt([
            'email' => $request->email,
            'password' => $request->password
        ], true)) {
            throw ValidationException::withMessages([
                'email' => '認証に失敗しました',
            ]);
        }

        // セッションIDの再生成(セキュリティ対策)
        $request->session()->regenerate();

        // 本来見たかったページへリダイレクト
        return redirect()->intended('/listing');
    }

    // ログアウト処理
    public function destroy(Request $request)
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return redirect()->route('listing.login');
    }
}

ルート(routes/web.php)を設定

use App\Http\Controllers\AuthController;

Route::get('/login', [AuthController::class, 'create'])->name('login');           // ログインフォーム
Route::post('/login', [AuthController::class, 'store'])->name('login.store');     // ログイン処理
Route::delete('/logout', [AuthController::class, 'destroy'])->name('logout');     // ログアウト

ログイン画面(Inertia.js + Vue/React)

Inertia + Vue の例(resources/js/Pages/Auth/Login.vue):

<template>
  <form @submit.prevent="submit">
    <div>
      <label>メールアドレス</label>
      <input v-model="form.email" type="email" />
      <p v-if="form.errors.email">{{ form.errors.email }}</p>
    </div>

    <div>
      <label>パスワード</label>
      <input v-model="form.password" type="password" />
      <p v-if="form.errors.password">{{ form.errors.password }}</p>
    </div>

    <button type="submit">ログイン</button>
  </form>
</template>

<script setup>
import { useForm } from '@inertiajs/inertia-vue3'

const form = useForm({
  email: '',
  password: '',
})

function submit() {
  form.post('/login')
}
</script>

補足:セキュリティ上の注意点

認証失敗時には「どこが間違っているか」は表示せず、**「認証に失敗しました」**のようなメッセージを使うとよい。
認証成功後は session()->regenerate() を実行し、セッション攻撃を防ぐ。

-エンジニア初心者がPHP/Laravelを学ぶ, 仕事