wake-up-neo.com

Wie teste ich die Authentifizierung per API mit Laravel Passport?

Ich versuche die Authentifizierung mit Laravel's Passport zu testen und es gibt keine Möglichkeit ... Immer wenn ein 401 von diesem Client ungültig ist, lasse ich Sie, was ich versucht habe:

Meine Phpunit-Konfiguration ist diejenige, die von der Basis mit Laravel stammt

tests/TestCase.php

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, DatabaseTransactions;

    protected $client, $user, $token;

    public function setUp()
    {
        parent::setUp();

        $clientRepository = new ClientRepository();
        $this->client = $clientRepository->createPersonalAccessClient(
            null, 'Test Personal Access Client', '/'
        );
        DB::table('oauth_personal_access_clients')->insert([
            'client_id' => $this->client->id,
            'created_at' => date('Y-m-d'),
            'updated_at' => date('Y-m-d'),
        ]);
        $this->user = User::create([
            'id' => 1,
            'name' => 'test',
            'lastname' => 'er',
            'email' => '[email protected]',
            'password' => bcrypt('secret')
        ]);
        $this->token = $this->user->createToken('TestToken', [])->accessToken;
    }
}

tests/Feature/AuthTest.php

class AuthTest extends TestCase
{
    use DatabaseMigrations;

    public function testShouldSignIn()
    {
        // Arrange
        $body = [
            'client_id' => (string) $this->client->id,
            'client_secret' => $this->client->secret,
            'email' => '[email protected]',
            'password' => 'secret',
        ];
        // Act
        $this->json('POST', '/api/signin', $body, ['Accept' => 'application/json'])
        // Assert
        ->assertStatus(200)
        ->assertJsonStructure([
            'data' => [
                'jwt' => [
                    'access_token',
                    'expires_in',
                    'token_type',
                ]
            ],
            'errors'
        ]);
    }
}

Meine handliche Authentifizierung mit Reisepass zu Testzwecken

routes/api.php

Route::post('/signin', function () {
    $args = request()->only(['email', 'password', 'client_id', 'client_secret']);
    request()->request->add([
        'grant_type' => 'password',
        'client_id' => $args['client_id'] ?? env('PASSPORT_CLIENT_ID', ''),
        'client_secret' => $args['client_secret'] ?? env('PASSPORT_CLIENT_SECRET', ''),
        'username' => $args['email'],
        'password' => $args['password'],
        'scope' => '*',
    ]);
    $res = Route::dispatch(Request::create('oauth/token', 'POST'));
    $data = json_decode($res->getContent());
    $isOk = $res->getStatusCode() === 200;
    return response()->json([
        'data' => $isOk ? [ 'jwt' => $data ] : null,
        'errors' => $isOk ? null : [ $data ]
    ], 200);
});
13
dddenis

So können Sie dies implementieren, damit es tatsächlich funktioniert.

Zunächst sollten Sie die Installation von db: seeds und Passport ordnungsgemäß implementieren. .

Zweitens müssen Sie keine eigene Route erstellen, um zu überprüfen, ob dies funktioniert (grundlegende Passport-Antworten sind hierfür ausreichend).

Hier ist eine Beschreibung, wie es in meiner Installation (Laravel 5.5) funktioniert hat ...

In meinem Fall brauche ich nur einen Passport Client, deshalb habe ich eine andere Route für die API-Autorisierung (api/v1/login) Nach erstellt Geben Sie nur Benutzername und Passwort an. Sie können mehr darüber lesen hier .

Glücklicherweise werden in diesem Beispiel auch grundlegende Passport-Autorisierungstests behandelt.

Um Ihre Tests erfolgreich durchzuführen, lautet die Grundidee:

  1. Erstellen Sie Passschlüssel bei der Testeinrichtung.
  2. Setzen Sie die Datenbank mit Benutzern, Rollen und anderen Ressourcen ein, die möglicherweise benötigt werden.
  3. Erstellen Sie einen .env - Eintrag mit PASSPORT_CLIENT_ID (Optional - Passport erstellen Sie immer password grant token Mit der ID = 2 leer db).
  4. Verwenden Sie diese ID, um das richtige client_secret aus der Datenbank abzurufen.
  5. Und dann führen Sie Ihre Tests durch ...

Codebeispiele ...

ApiLoginTest.php

/**
* @group apilogintests
*/    
public function testApiLogin() {
    $body = [
        'username' => '[email protected]',
        'password' => 'admin'
    ];
    $this->json('POST','/api/v1/login',$body,['Accept' => 'application/json'])
        ->assertStatus(200)
        ->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}
/**
 * @group apilogintests
 */
public function testOauthLogin() {
    $oauth_client_id = env('PASSPORT_CLIENT_ID');
    $oauth_client = OauthClients::findOrFail($oauth_client_id);

    $body = [
        'username' => '[email protected]',
        'password' => 'admin',
        'client_id' => $oauth_client_id,
        'client_secret' => $oauth_client->secret,
        'grant_type' => 'password',
        'scope' => '*'
    ];
    $this->json('POST','/oauth/token',$body,['Accept' => 'application/json'])
        ->assertStatus(200)
        ->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}

Hinweise:

Zugangsdaten müssen natürlich geändert werden.

PASSPORT_CLIENT_ID muss, wie zuvor erläutert, 2 sein.

Die JsonStructure-Überprüfung ist redundant, da wir nur bei erfolgreicher Autorisierung 200 Antworten erhalten. Wenn Sie jedoch eine zusätzliche Bestätigung wünschen, besteht diese auch ...

TestCase.php

public function setUp() {
    parent::setUp();
    \Artisan::call('migrate',['-vvv' => true]);
    \Artisan::call('passport:install',['-vvv' => true]);
    \Artisan::call('db:seed',['-vvv' => true]);
}

Hinweise:

Hier erstellen wir relevante Einträge zu db, die in unseren Tests benötigt werden. Denken Sie also daran, Benutzer mit Rollen usw. hier zu haben.

Schlussnoten ...

Dies sollte ausreichen, damit Ihr Code funktioniert. Auf meinem System ist dies alles grün und funktioniert auch auf meinem Gitlab CI-Läufer.

Schließlich überprüfen Sie bitte auch Ihre Middlewares auf Routen. Vor allem, wenn Sie mit Dingo (oder jwt von Thymon ) experimentiert haben Paket.

Die einzige Middleware, die Sie möglicherweise für Passport Autorisierungsrouten in Betracht ziehen, ist throttle, um einen gewissen Schutz vor Brute-Force-Angriff .

Randnotiz ...

Passport und Dingo haben völlig unterschiedliche JWT Implementierungen.

In meinen Tests verhält sich nur Passport richtig und ich gehe davon aus, dass dies der Grund ist, warum Dingo wird nicht mehr gepflegt.

Hoffe, es wird Ihr Problem lösen ...

19
Bart

Um den Pass zu testen, mussten Sie keinen echten Benutzer und kein echtes Passwort angeben. Sie können einen Test erstellen.
Sie können Passport::actingAs Oder mit setup() verwenden.

Für actingAs können Sie gerne tun

public function testServerCreation()
{
    Passport::actingAs(
        factory(User::class)->create(),
        ['create-servers']
    );

    $response = $this->post('/api/create-server');

    $response->assertStatus(200);
}

und mit setUp() können Sie dies erreichen durch

public function setUp()
    {
        parent::setUp();
        $clientRepository = new ClientRepository();
        $client = $clientRepository->createPersonalAccessClient(
            null, 'Test Personal Access Client', $this->baseUrl
        );
        DB::table('oauth_personal_access_clients')->insert([
            'client_id' => $client->id,
            'created_at' => new DateTime,
            'updated_at' => new DateTime,
        ]);
        $this->user = factory(User::class)->create();
        $token = $this->user->createToken('TestToken', $this->scopes)->accessToken;
        $this->headers['Accept'] = 'application/json';
        $this->headers['Authorization'] = 'Bearer '.$token;
    }

Weitere Informationen erhalten Sie hier und https://laravel.com/docs/5.6/passport#testing .

4

Laravel Passport wird mit einigen Testhilfen ausgeliefert mit dem Sie Ihre authentifizierten API-Endpunkte testen können.

Passport::actingAs(
    factory(User::class)->create(),
);
4
Dwight

Ich denke, die ausgewählte Antwort ist wahrscheinlich die robusteste und bisher beste hier, aber ich wollte eine Alternative anbieten, die für mich funktioniert, wenn Sie nur schnell Tests hinter dem Reisepass bestehen müssen, ohne viel Setup.

Wichtiger Hinweis: Ich denke, wenn Sie viel davon tun, ist dies nicht der richtige Weg und andere Antworten sind besser. Aber nach meiner Einschätzung scheint dies zu funktioniert einfach

Hier ist ein vollständiger Testfall, in dem ich einen Benutzer POST an einen Endpunkt senden und sein Autorisierungstoken verwenden muss, um die Anforderung zu stellen.

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

use App\Models\User;
use Laravel\Passport\Passport;

class MyTest extends TestCase
{
    use WithFaker, RefreshDatabase;

    public function my_test()
    {
        /**
        *
        * Without Artisan call you will get a passport 
        * "please create a personal access client" error
        */
        \Artisan::call('passport:install');

        $user = factory(User::class)->create();
        Passport::actingAs($user);

        //See Below
        $token = $user->generateToken();

        $headers = [ 'Authorization' => 'Bearer $token'];
        $payload = [
            //...
        ];



        $response = $this->json('POST', '/api/resource', $payload, $headers);

        $response->assertStatus(200)
                ->assertJson([
                    //...
                ]);

    }
}

Und der Übersichtlichkeit halber hier die Methode generateToken() im Benutzermodell, die das Merkmal HasApiTokens nutzt.

public function generateToken() {
    return $this->createToken('my-oauth-client-name')->accessToken; 
}

Dies ist meiner Meinung nach ziemlich rau und bereit. Wenn Sie beispielsweise das Merkmal RefreshDatabase verwenden, müssen Sie den Befehl passport: install in jeder Methode so ausführen. Es gibt vielleicht einen besseren Weg, dies über das globale Setup zu tun, aber ich bin ziemlich neu in PHPUnit, also mache ich es (vorerst) so.

3
Eli Hooten

Ich war nicht mit dem Passport-Tool vertraut, auf das sich Dwight bezieht, als ich das geschrieben habe, daher ist es möglich, dass das eine einfachere Lösung ist. Aber hier ist etwas, das helfen kann. Es wird ein Token für Sie erstellt, das Sie dann auf Ihren Mock-API-Aufruf anwenden können.

/**
 * @param Authenticatable $model
 * @param array $scope
 * @param bool $personalAccessToken
 * @return mixed
 */
public function makeOauthLoginToken(Authenticatable $model = null, array $scope = ['*'], $personalAccessToken = true)
{
    $tokenName = $clientName = 'testing';
    Artisan::call('passport:client', ['--personal' => true, '--name' => $clientName]);
    if (!$personalAccessToken) {
        $clientId = app(Client::class)->where('name', $clientName)->first(['id'])->id;
        Passport::$personalAccessClient = $clientId;
    }
    $userId = $model->getKey();
    return app(PersonalAccessTokenFactory::class)->make($userId, $tokenName, $scope)->accessToken;
}

Dann wende es einfach auf die Überschriften an:

$user = app(User::class)->first($testUserId);
$token = $this->makeOauthLoginToken($user);
$headers = ['authorization' => "Bearer $token"];
$server = $this->transformHeadersToServerVars($headers);

$body = $cookies = $files = [];
$response = $this->call($method, $uri, $body, $cookies, $files, $server);

$content = $response->getContent();
$code = $response->getStatusCode();

Wenn Sie in der Lage sein müssen, das Token zu analysieren, versuchen Sie Folgendes:

/**
 * @param string $token
 * @param Authenticatable $model
 * @return Authenticatable|null
 */
public function parsePassportToken($token, Authenticatable $model = null)
{
    if (!$model) {
        $provider = config('auth.guards.passport.provider');
        $model = config("auth.providers.$provider.model");
        $model = app($model);
    }
    //Passport's token parsing is looking to a bearer token using a protected method.  So a dummy-request is needed.
    $request = app(Request::class);
    $request->headers->add(['authorization' => "Bearer $token"]);
    //Laravel\Passport\Guards\TokenGuard::authenticateViaBearerToken() expects the user table to leverage the
    //HasApiTokens trait.  If that's missing, a query macro can satisfy its expectation for this method.
    if (!method_exists($model, 'withAccessToken')) {
        Builder::macro('withAccessToken', function ($accessToken) use ($model) {
            $model->accessToken = $accessToken;
            return $this;
        });
        /** @var TokenGuard $guard */
        $guard = Auth::guard('passport');
        return $guard->user($request)->getModel();
    }
    /** @var TokenGuard $guard */
    $guard = Auth::guard('passport');
    return $guard->user($request);
}
1
Claymore