👀

LaravelのactingAsは何をしているのか?

2023/12/04に公開

前提

Laravelの認証機能には、「ガード」と「プロバイダ」と言う概念が存在しており、それらで構成されています。

ガードは各リクエストごとに、どのようにユーザーを認証するかを定義します。たとえば、Laravelにはセッションストレージとクッキーを使いながら状態を維持するsessionガードが用意されています。
プロバイダは永続ストレージから、どのようにユーザーを取得するかを定義します。LaravelはEloquentとデータベースクリエビルダを使用しユーザーを取得する機能を用意しています。しかし、アプリケーションの必要性に応じて、自由にプロバイダを追加できます。

認証 10.x Laravel

actingAsとは?

Laravelのセッションは通常、現在認証しているユーザーの状態を維持するために使用します。したがって、actingAs ヘルパメソッドは、指定ユーザーを現在のユーザーとして認証する簡単な方法を提供します。たとえば、モデルファクトリを使用して、ユーザーを生成および認証できます。

HTTPテスト 8.x Laravel

認証が必要な処理のテストを行う際に、認証した状態を作り出してくれるヘルパメソッドになります。RestAPIにおける認証の場合、通常はトークンをリクエストに含めて検証すると思います。となると上記のヘルパメソッドはテスト実行のたびにトークンを生成しているのか?と思い気になったので中身の実装がどうなっているのか気になったので調べてみることにしました。

以下が actingAs の実例になります。

// 認証が必要なAPIに対してリクエストして200が返却されること
$response = $this->actingAs($user)
            ->get('/api/auth/xxx')
            ->seeStatusCode(200);

Laravelでは認証をどのように判断しているのか?

通常、Laravelで認証判定をする際には、以下のように記述します。
するとIlluminate\Auth\Middleware\Authenticate の handleメソッドが呼ばれます。

Route::get('/flights', function () {
    // 認証済みユーザーのみがこのルートにアクセス可能
})->middleware('auth');
public function handle($request, Closure $next, ...$guards)
{
    $this->authenticate($request, $guards);

    return $next($request);
}

protected function authenticate($request, array $guards)
{
    if (empty($guards)) {
        $guards = [null];
    }

    foreach ($guards as $guard) {
        if ($this->auth->guard($guard)->check()) {
            return $this->auth->shouldUse($guard);
        }
    }

    $this->unauthenticated($request, $guards);
}

$this->auth は、AuthServiceProviderAuthManagerを紐づけており、Guard メソッドを実行している。

guard:session, token, 自作のどれかのガードを取得する

  • Tokenの場合だと、TokenDriverクラスが返却される

check:認証判定を実施(ユーザーを取得できれば認証OK)

  • Guardインターフェースを実装したクラスの user メソッドを実行

user(TokenDriver):ユーザー情報を取得

  • 対象ガードのuserプロパティに値が代入されて入れば、そのユーザー情報を返却
  • リクエストの中身を確認してユーザー情報を取得、あれば返却
    • bearerTokenや、api_token というキーからトークンを取得し、usersテーブルのapi_tokenカラムと照合して、対象ユーザーを取得する。

上記の手順を経て、無事ユーザーが取得できれば認証成功という判定になる。

actingAsのコードを追ってみる

今回はトークン認証の前提でコードを追っていきます。

public function actingAs(UserContract $user, $driver = null)
{
    $this->be($user, $driver);

    return $this;
}

public function be(UserContract $user, $driver = null)
{
    $this->app['auth']->guard($driver)->setUser($user);

    $this->app['auth']->shouldUse($driver);
}

actingAsbe を実行しているだけで全く同じ

Laracast に以下のような推測がある。なるほど…

一時期はbe()だったのかもしれないが、これは名前の選択がまずかったというフィードバックがあった。テイラーはそれをactingAs()に変更したが、壊れるのを避けるために古いものを残した。

指定されたユーザーオブジェクトを認証ガードの現在の認証ユーザーとして設定する

認証判定の際に見たガードクラスを取得する処理ですが、先ほどは check メソッドを実行していましたが、今回は setUser メソッドを実行しています。

確認してみると、ガードクラスの user プロパティに引数に与えたユーザーオブジェクトを代入しています。

public function setUser(AuthenticatableContract $user)
{
    $this->user = $user;

    return $this;
}

Laravelの認証判定ロジックでは、ユーザーが取得できれば認証完了でした。先ほどの内容を再度見てみます。

user(TokenDriver):ユーザー情報を取得

  • 対象ガードのuserプロパティに値が代入されて入れば、そのユーザー情報を返却
  • リクエストの中身を確認してユーザー情報を取得、あれば返却
    • bearerTokenや、api_token というキーからトークンを取得し、usersテーブルのapi_tokenカラムと照合して、対象ユーザーを取得する。

対象ガードのuserプロパティに値が代入されて入れば、そのユーザー情報を返却

$this→usernull でなければ、そのユーザー情報を返却するという実装になってます。
つまり、 actingAs 内の setUser で認証ガードの user プロパティにユーザーを設定することで実質認証できたことと同じ挙動になります。

public function user()
{
    if (! is_null($this->user)) {
        return $this->user;
    }

    $user = null;

    $token = $this->getTokenForRequest();

    if (! empty($token)) {
        $user = $this->provider->retrieveByCredentials(
            [$this->storageKey => $token]
        );
    }

    return $this->user = $user;
}

まとめ

  • Laravelの認証にはガードとプロバイダと言う概念がある
  • 認証判定はユーザーが取得できるかどうか
  • actingAsは利用する認証ガードクラスのuserプロパティに値を代入し、その情報を返却することで認証判定をクリアしている

Discussion