J'essaie de tester l'authentification avec le passeport de Laravel et il n'y a aucun moyen ... toujours reçu un 401 de ce client n'est pas valide, je vous laisse ce que j'ai essayé:
Ma configuration phpunit est celle qui vient de la base avec laravel
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;
}
}
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'
]);
}
}
Mon authentification pratique avec passeport à des fins de test
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);
});
Voici comment vous pouvez l'implémenter, pour que cela fonctionne réellement.
Tout d'abord, vous devez correctement implémenter db: seeds et Installation de passeport .
Deuxièmement, vous n'avez pas besoin de créer votre propre itinéraire pour vérifier si cela fonctionne (les réponses de base Passport sont assez bien pour cela).
Voici donc une description de son fonctionnement dans mon installation (Laravel 5.5) ...
Dans mon cas, je n'ai besoin que d'un client Passport , c'est pourquoi, j'ai créé une autre route, pour l'autorisation api (api/v1/login
), pour ne fournir que nom d'utilisateur et mot de passe. Vous pouvez en savoir plus ici .
Heureusement, cet exemple couvre également le test de base d'autorisation de passeport .
Donc, pour réussir vos tests, l'idée de base est:
.env
entrée avec PASSPORT_CLIENT_ID
(facultatif - Passeport créez toujours password grant token
avec id = 2 sur db vide).Exemples de code ...
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']);
}
Remarques:
Les pouvoirs doivent bien sûr être modifiés.
PASSPORT_CLIENT_ID doit être égal à 2, comme expliqué précédemment.
La vérification JsonStructure est redondante, car nous obtenons 200 réponses, uniquement si l'autorisation réussit. Cependant, si vous vouliez une vérification supplémentaire, cela passe également ...
TestCase.php
public function setUp() {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
Remarques:
Ici, nous créons des entrées pertinentes pour db, qui sont nécessaires dans nos tests. N'oubliez donc pas d'avoir des utilisateurs avec des rôles, etc. ici.
Notes finales ...
Cela devrait suffire pour que votre code fonctionne. Sur mon système, tout cela passe au vert et fonctionne également sur mon runner gitlab CI.
Enfin, veuillez également vérifier vos middlewares sur les itinéraires. Surtout, si vous expérimentiez avec dingo (ou jwt par thymon ) paquet.
Le seul middleware, vous pouvez envisager, s'appliquant à la route d'autorisation du passeport , est throttle
pour avoir une certaine protection contre attaque par force brute .
Note latérale ...
Passeport et dingo ont totalement différents implémentations jwt .
Dans mes tests, seul Passport se comporte de la bonne façon et je suppose que c'est la raison pour laquelle dingo n'est plus maintenu.
J'espère que cela résoudra votre problème ...
Pour tester le passeport, vous n'avez pas besoin d'aller pour un véritable utilisateur et un mot de passe, vous pouvez en créer un.
Vous pouvez utiliser Passport::actingAs
Ou par setup()
.
Pour actingAs
vous pouvez faire comme
public function testServerCreation()
{
Passport::actingAs(
factory(User::class)->create(),
['create-servers']
);
$response = $this->post('/api/create-server');
$response->assertStatus(200);
}
et avec setUp()
vous pouvez y parvenir en
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;
}
Vous pouvez obtenir plus de détails ici et https://laravel.com/docs/5.6/passport#testing .
Laravel Passport livré avec des aides de test que vous pouvez utiliser pour tester vos points de terminaison API authentifiés.
Passport::actingAs(
factory(User::class)->create(),
);
Je pense que la réponse sélectionnée est probablement la plus robuste et la meilleure ici jusqu'à présent, mais je voulais fournir une alternative qui a fonctionné pour moi si vous avez juste besoin de passer rapidement des tests derrière le passeport sans beaucoup de configuration.
Remarque importante: je pense que si vous allez faire beaucoup de cela, ce n'est pas la bonne façon et d'autres réponses sont meilleures. Mais à mon avis, cela semble juste travailler
Voici un cas de test complet où je dois supposer un utilisateur, POST à un point de terminaison, et utiliser leur jeton d'autorisation pour faire la demande.
<?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([
//...
]);
}
}
Et pour plus de clarté, voici la méthode generateToken()
dans le modèle User, qui exploite le trait HasApiTokens
.
public function generateToken() {
return $this->createToken('my-oauth-client-name')->accessToken;
}
C'est assez rude et prêt à mon avis. Par exemple, si vous utilisez le trait RefreshDatabase
, vous devez exécuter la commande passport: install comme celle-ci dans chaque méthode. Il y a peut-être un meilleur moyen de le faire via une configuration globale, mais je suis assez nouveau sur PHPUnit, c'est ainsi que je le fais (pour l'instant).
Je ne connaissais pas l'outil de passeport auquel Dwight fait référence lorsque j'ai écrit cela, il est donc possible que ce soit une solution plus simple. Mais voici quelque chose qui peut vous aider. Il produit un jeton pour vous, que vous pouvez ensuite appliquer à votre appel mock-API.
/**
* @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;
}
Ensuite, vous l'appliquez simplement aux en-têtes:
$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();
Si vous devez pouvoir analyser le jeton, essayez ceci:
/**
* @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);
}