LaravelのactingAsは何をしているのか?
前提
Laravelの認証機能には、「ガード」と「プロバイダ」と言う概念が存在しており、それらで構成されています。
ガードは各リクエストごとに、どのようにユーザーを認証するかを定義します。たとえば、Laravelにはセッションストレージとクッキーを使いながら状態を維持する
session
ガードが用意されています。
プロバイダは永続ストレージから、どのようにユーザーを取得するかを定義します。LaravelはEloquentとデータベースクリエビルダを使用しユーザーを取得する機能を用意しています。しかし、アプリケーションの必要性に応じて、自由にプロバイダを追加できます。
actingAsとは?
Laravelのセッションは通常、現在認証しているユーザーの状態を維持するために使用します。したがって、
actingAs
ヘルパメソッドは、指定ユーザーを現在のユーザーとして認証する簡単な方法を提供します。たとえば、モデルファクトリを使用して、ユーザーを生成および認証できます。
認証が必要な処理のテストを行う際に、認証した状態を作り出してくれるヘルパメソッドになります。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
は、AuthServiceProvider で AuthManager
を紐づけており、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);
}
actingAs
は be
を実行しているだけで全く同じ
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→user
が null
でなければ、そのユーザー情報を返却するという実装になってます。
つまり、 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