パーソナルアクセスクライアントでLaravelTest7とLaravelPassport 9.3を使用すると、「非オブジェクトのプロパティ「id」を取得しようとしています」という例外が発生します。

Aug 19 2020

ステートレスAPIと一緒に(公開鍵に基づく)カスタム認証スキームを設計しており、Passportが認証後の要求のニーズを満たすことを決定しました。

認証が成功し、ユーザーが認証されたとすると、ユーザーはパーソナルアクセストークンを受け取り、それ以降のすべてのリクエストにそのトークンを使用します。私が経験している問題は(さまざまなフォーラムやStack Overflowを何度も検索した後でも)、Laravelの組み込みテストスイートをcreateToken()メソッドで使用すると、(明らかに一般的な)例外が生成されることです。

「ErrorException:非オブジェクトのプロパティ「id」を取得しようとしています」。

Tinkerを使用して手動でユーザーを作成し、Tinkerを使用してトークンを作成することができます。ただし、認証後にこのプロセスを自動化しようとすると、問題が発生します。

認証後の関連するコードスニペットは次のとおりです。

            Auth::login($user); $user = Auth::user();
            $tokenResult = $user->createToken('Personal Access Token');
            $token = $tokenResult->token;

            $token->expires_at = Carbon::now()->addWeeks(1); $token->save();            

            return response()->json([
                "access_token" => $tokenResult->accessToken, "token_type" => "Bearer", "expires_at" => Carbon::parse( $tokenResult->token->expires_at)->toDateTimeString()
            ],
            200);

ユーザーがログインしていることを確認するために、ユーザーに対して手動でAuth :: loginを呼び出しました。Auth:: user()はユーザーを返します(nullではありません)。コードの3行目を実行すると、次のミニスタックトレースで例外がスローされます(要求された場合は完全なスタックトレースを提供できます)。

laravel\passport\src\PersonalAccessTokenFactory.php:100
laravel\passport\src\PersonalAccessTokenFactory.php:71
laravel\passport\src\HasApiTokens.php:67
app\Http\Controllers\Auth\LoginController.php:97
laravel\framework\src\Illuminate\Routing\Controller.php:54
laravel\framework\src\Illuminate\Routing\ControllerDispatcher.php:45

これを数回実行してからデバッグまで-クラスが呼び出されてロードされ、クライアントがControllerDispatcher-> Client :: find(id)を介して検出され、ClientRepositoryで検出されたように見えても、PersonalAccessTokenFactoryに到達すると、 $client passed in is null (which explains why the $client-> idが見つかりませんが、この時点で$ clientがnullである理由はわかりません)。

protected function createRequest($client, $userId, array $scopes)
{
    $secret = Passport::$hashesClientSecrets ? Passport::$personalAccessClientSecret : $client->secret;

    return (new ServerRequest)->withParsedBody([
        'grant_type' => 'personal_access',
        'client_id' => $client->id,
        ...
}

ドキュメントや他の投稿からのガイダンスで私が行った/試したこと:

  • Tinkerでユーザーを手動で作成し、Tinkerを介してトークンを作成しました-これは機能します。
  • トークンを生成する前に、ユーザーがログインしていることを確認してください。
  • passport:install(および--forceオプションの追加)
  • パーソナルアクセスクライアントがpassport:client--personalで生成されていることを確認
  • AuthServiceProvider :: boot()にClientIDとクライアントシークレット(.env内)が含まれていることを確認しました。
  • 移行:更新、続いてパスポート:インストール--force
  • Passportを完全に削除し、すべてのファイル、キー、移行、およびDBエントリを削除した後、passportのmigrate:refreshと再インストールを行い、追加のパーソナルアクセスクライアントを生成します(passport:install中に生成された場合でも)。

この時点で他にどこを探すべきか/他に何を試すべきかわからないので、助けやガイダンスをいただければ幸いです。

回答

grantley Aug 21 2020 at 22:41

私は最終的に解決策を発見しました。問題は多層的であり、テストとPassport Personal AccessClientに関する古いLaravelドキュメントに部分的に関係しています。

問題の最初の部分は、ユニットテストでトレイトRefreshDatabaseを使用することと関係がありました。これにより、空のデータセットを含むモックデータベースが作成されるため、クライアント自体は実際のデータベースと.envファイルに存在しますが、テストを実行すると、テストではそれらのクライアントがモックデータベースに存在するものとして認識されません。この問題を解決するには、テストを実行する前に、セットアップ関数でクライアントを作成する必要があります。

public function setUp() : void
{
    parent::setUp();
    $this->createClient(); //Private method->Full code below
}

これにより、テスト中にクライアントがnullになる問題が解決されますが、Laravel 7以降、Laravelは、IDとクライアントシークレットを.envファイル内に保持する必要があるというパーソナルアクセスクライアントの要件を追加しました。テストを実行すると、テストは.env内の実際のクライアントIDとシークレットを確認し、モックデータベースで作成および保存されたクライアントでこれらを検証できず、「クライアント認証に失敗しました」という別の例外が返されます。

この問題の解決策は、メインプロジェクトディレクトリに.env.testingファイルを作成し、.envファイルの内容をそのファイルにコピーして、作成したメインのパーソナルアクセスクライアントの値とともに以下のキーが存在することを確認するか、シークレットをコピーすることです。テスト用に生成されたクライアントから(後者をお勧めします)。

 PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1

 PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=unhashed-client-secret-value

次に、以下のコードを使用して、$ clientSecret値が.env.testingファイルのキー値と同じであることを確認します。

private function createClient() : void
{
    $clientRepository = new ClientRepository(); $client = $clientRepository->createPersonalAccessClient( null, 'Test Personal Access Client', 'http://localhost' ); DB::table('oauth_personal_access_clients')->insert([ 'client_id' => $client->id,
        'created_at' => new DateTime,
        'updated_at' => new DateTime,
    ]);

    $clientSecret = 'unhashed-client-secret-value'; $client->setSecretAttribute($clientSecret); $client->save();
}

これにより、新しいクライアントが作成され、属性シークレットが変数の値に設定され、モックデータベースシークレットが同じ値を含むように更新されます。うまくいけば、これは同じ問題を抱えている人に役立つでしょう。