Symfonyの認証機能はなぜ変わった?GuardからPassportへ、実際に実装してみた

Symfonyの認証機能はなぜ変わった?
GuardからPassportへ、実際に実装してみた

1. 昔のやり方:ちょっと複雑だった「Guard」

以前のSymfonyでログイン機能を実装したことがあるなら、Guardという仕組みをご存知かもしれません。Guardは細かい制御ができる反面、必要な手順がとても多くて大変でした

例えば、ログイン認証のコードを書くとき、認証の流れのほとんどすべてを一つのファイルに詰め込む必要がありました。

// src/Security/LoginFormAuthenticator.php (Symfony 5以前の例)

use Symfony\Component\Security\Guard\Authenticator\AbstractGuardAuthenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
// ... 他にも多くのuse文が必要

class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
    // 1. どのリクエスト(URLや送信方法)を認証の対象にするか決める
    public function supports(Request $request): bool
    {
        // ルート名とメソッドをここでチェック
        return 'app_login' === $request->attributes->get('_route')
            && $request->isMethod('POST');
    }

    // 2. フォームからユーザー名やパスワード(クレデンシャル)を取り出す
    public function getCredentials(Request $request)
    {
        return [
            'email' => $request->request->get('email'),
            'password' => $request->request->get('password'),
            // ... (CSRFトークンなどもここで手動で取得)
        ];
    }
    
    // 3. ユーザー名をもとにデータベースからユーザーを探す (getUser)
    // 4. パスワードが正しいかチェックする (checkCredentials)
    // 5. ログイン成功時の画面遷移を定義する (onAuthenticationSuccess)
    // 6. ログイン失敗時のエラー処理を定義する (onAuthenticationFailure)
    // 7. 匿名アクセスを処理する (start)
    // ... など、とにかくたくさんの決まった処理(定型的なメソッド)を書く必要がありました。
}

このように、Guardでは認証の各ステップを開発者が細かく手動で記述する必要があり、「もっと簡単にならないかな?」と私自身もいつも感じていました。

2. なぜ変わったのか?Guardの課題を解決するため

私が感じていた「もっとシンプルにできないか」という疑問を解決したのが「Passport」という新しい仕組みです。

この認証機能の刷新は、単にコードを短くするためだけでなく、Guardが抱えていた以下の2つの課題を解決し、より現代的な認証の流れを作るのが目的でした。

課題1: やることが多すぎてゴチャゴチャになる

Guardでは、リクエストの判定、パスワードの確認、ユーザーの検索など、認証に必要なすべての作業が1つのクラス(Authenticator)に集中していました。これにより、クラスが大きくなり、ロジックが複雑で理解しにくくなっていました。

課題2: 決まりきったコード(定型コード)が多すぎる

上記コード例で見たように、認証の本質ではない、決まりきった処理(supportsgetCredentialsなどのメソッド)を大量に書く必要があり、これが開発の負担になっていました。

この課題を解決するため、Passportは認証の「決まりきった処理」をフレームワークが自動で引き受けるという考え方に切り替えたのです。

3. 今のやり方:「Passport」でとても簡単に

新しいプロジェクトでPassportベースの認証システムを実装して、そのシンプルさに本当に驚きました。

(ちなみに、Passport自体はSymfony 5.1から登場し始めましたが、Symfony 6で完全にこの方式に変わりました。)

Passportでは、Guardで必要だったたくさんのメソッドが、authenticate()という一つのメソッドにまとまりました。

// src/Security/LoginFormAuthenticator.php (Symfony 6以降の例)

use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;

class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
    // 認証情報を集めて、Passportオブジェクトを作るたった一つのメソッド
    public function authenticate(Request $request): Passport
    {
        $email = $request->request->get('email');
        $password = $request->request->get('password');
        
        // 「誰が (UserBadge: ユーザー名)」「どうやって (PasswordCredentials: パスワード)」認証を試みているかを伝えるだけ
        return new Passport(
            new UserBadge($email),
            new PasswordCredentials($password)
        );
    }
    
    // ログイン後のリダイレクト先を決めるだけでOK
    protected function getLoginUrl(Request $request): string
    {
        return $this->urlGenerator->generate(self::LOGIN_ROUTE);
    }
}

私たちは、「誰が」「どうやって」ログインしようとしているかを宣言するだけでよくなりました。残りの「ユーザーを探す」「パスワードが正しいか確認する」といった決まった処理は、フレームワークが全部自動でやってくれるようになったのです。この役割分担によって、コードが劇的に減り、全体がとても分かりやすくなりました。

4. 機能拡張もシンプルに:「バッジ」によるカスタム要素の追加

Passportへの変更は、コードを減らしただけでなく、認証フローの拡張性も高めています。

Guard時代は、標準の認証にカスタムな条件(例:二段階認証や特定のチェック)を追加するのが大変でした。

例えば、複数の会社が同じシステムを使うマルチテナント認証を実装する機会がありました。以前は、テナントIDのチェックを複数のメソッドに分けて書く必要がありましたが、Passportでは、認証情報にテナントIDを「バッジ (Badge)」という形で付け加えるだけで済みます。

Passportは、認証に必要な追加の条件をこのバッジとして認識し、フレームワークが適切に処理してくれます。

// マルチテナント認証の例

use App\Security\TenantBadge;

public function authenticate(Request $request): Passport
{
    // フォームからユーザーID、パスワード、テナントIDを取得
    $email = $request->request->get('email');
    $password = $request->request->get('password');
    $tenantId = $request->request->get('tenantId'); // カスタム項目
    
    return new Passport(
        new UserBadge($email),
        new PasswordCredentials($password),
        // Passportに「テナントID」というバッジを追加するだけ
        [new TenantBadge($tenantId)] 
    );
}

このように、機能の追加や変更がとてもシンプルになり、認証の核となるコードがカスタムなロジックで複雑になるのを防げるようになりました。

5. まとめ:実践してみて感じたこと

今回、実際にPassportベースの認証システムを実装してみて、その便利さを改めて実感しました。もし以前のSymfonyでログイン機能に苦労した経験があるなら、ぜひ今のやり方を試してみてほしいです。そのシンプルさとわかりやすさにきっと驚くはずです。

このブログ記事が、あなたの開発のヒントになれば幸いです。