How to generate Passport oauth_clients for testing database? - laravel

I'm testing api endpoint which is authenticated with laravel passport. I'm writing a feature test for checking if oauth/token can return a valid access_token. It is working fine in postman but I am using a different database for testing so when I run the test I'm always getting 400 error. Though I'm able to test all authenticated routes but I'm stuck when I want to test api/login or oauth/token endpoints. I'm running artisan command to install passport for test database. How to get client id and secret?
In TestCase class
public function setUp(): void
{
parent::setUp();
Artisan::call('migrate', ['-vvv' => true]);
Artisan::call('passport:install', ['-vvv' => true]);
Artisan::call('db:seed', ['-vvv' => true]);
}
LoginTest class
public function test_it_returns_a_valid_token_if_credentials_do_match()
{
$client = Passport::client();
$user = User::factory()->create([
'email' => $email = 'test#test.com',
'password' => $password = '12345678'
]);
$body = [
'client_id' => $client->id,
'client_secret' => $client->secret,
'username' => $email,
'password' => $password
];
$this->json('POST', '/oauth/token', $body)
->assertStatus(200);
}
Here $client = Passport::client() return null. I didn't find any solution to get oauth client credentials.

Actually $client = Passport::client(); doesn't return client.
I have found a solution to create client from LoginTest.
$user = User::factory()->create([
'email' => $email = 'test#test.com',
'password' => $password = '12345678'
]);
$client = ClientFactory::new()->asPasswordClient()->create(['user_id' => $user->id]);
$response = $this->json(
'POST', '/oauth/token',
[
'grant_type' => 'password',
'client_id' => $client->id,
'client_secret' => $client->secret,
'username' => $user->email,
'password' => $password,
]
)
->assertStatus(200);

Related

Laravel Passport - Feature test returns unauthorized 401

I'm trying to test my login endpoint where a successful response would return the access_token among other things.
I'm using RefreshDatabase, so I changed the login method on the controller to retrieve the client_secret via a DB call. I tested with a dd() and I can confirm that the client_secret changes on each phpunit run in the terminal. The credentials are correct and the API endpoint works - just not when it's run via a test. For example, I have the passport tables set up on my mysql server and I can login successfully when running Postman. It's only when trying to run a test do I get a 401 error.
Here is my AuthTest
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class AuthTest extends TestCase
{
use RefreshDatabase;
/**
* #test
*/
public function a_user_receives_an_access_token()
{
\Artisan::call('passport:install');
$user = factory('App\User')->create();
$response = $this->json('POST', '/api/login', [
'username' => $user->email,
'password' => 'password'
]);
$response
->assertJson([
'access_token' => true
]);
}
}
routes/api.php
Route::post('login', 'AuthController#login');
AuthController#login:
public function login(Request $request) {
$http = new \GuzzleHttp\Client;
try {
$response = $http->post(config('services.passport.login_endpoint'), [
'form_params' => [
'grant_type' => 'password',
'client_id' => '2', //config('services.passport.client_id')
'client_secret' => DB::table('oauth_clients')->where('id', 2)->pluck('secret')[0], //config('services.passport.client_secret'),
'username' => $request->username,
'password' => $request->password
]
]);
return $response->getBody();
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
if ($e->getCode() == 400 || $e->getCode() == 401) {
return response()
->json([
'status' => $e->getCode(),
'message' => 'Your email and/or password are incorrect',
'expanded' => $e->getMessage()
]);
}
return response()
->json([
'status' => $e->getCode(),
'message' => $e->getMessage()
]);
}
}
I took a look at this question and the accepted answer: How to test authentication via API with Laravel Passport?
I am unable to use the following
public function setUp() {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
This results in an error:
PHP Fatal error: Declaration of Tests\Feature\AuthTest::setUp() must be compatible with Illuminate\Foundation\Testing\TestCase::setUp()
Edit: I just added
public function setUp() :void {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
but the problem still persists
Edit again:
If I test the oauth route directly, it passes.
public function testOauthLogin() {
$oauth_client_id = 2;
$oauth_client = OAuthClient::findOrFail($oauth_client_id);
$user = factory('App\User')->create();
$body = [
'username' => $user->email,
'password' => 'password',
'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']);
}
But my custom endpoint that uses guzzle fails. I do not know why
Edit again:
I think the issue is with Guzzle, but I'm not sure. I found another implementation of what I'm trying to do, which is the following:
public function login(Request $request) {
$request->request->add([
'username' => $request->username,
'password' => $request->password,
'grant_type' => 'password',
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'scope' => '*'
]);
$response = Route::dispatch(Request::create(
'oauth/token',
'POST'
));
}
The above works.

Guzzle Not Sending Grant Type to Laravel Passport

Im a little stumped with my code, I am running Laravel 6 with Guzzle Http Client version 6.3.3.
I have opted to use a trait which I use on my API Gateway for communicating with micro services instead of bloating code base with repeated code.
The Trait
public function performRequest($method, $requestUrl, $formParams = [], $headers =[])
{
$core = env('CORE_URI');
$client = new Client([
'base_uri' => $core,
]);
$response = $client->request($method, $requestUrl, ['form_params' => $formParams, 'headers' => $headers]);
return $response->getBody()->getContents();
}
The failing code (Not sending the OAuth Grant Type Password even though it works using postman)
$core_client_id = env('CORE_CLIENT_ID');
$core_client_secret = env('CORE_CLIENT_SECRET');
$username = $request->input('username');
$password = $request->input('password');
return $this->performRequest('POST','/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => $core_client_id,
'client_secret' => $core_client_secret,
'username' => $username,
'password' => $password,
'scope' => '',
],
'headers' => [
'content-type' => 'multipart/form-data',
]
]);
The Exception Guzzle is returning is 400 Bad Request 'Unsupported Grant Type'
I fixed it by removing the headers and form params and changing my code to send the data as an array instead.
Working Code
public function attemptLogin(Request $request)
{
$core_client_id = env('CORE_CLIENT_ID');
$core_client_secret = env('CORE_CLIENT_SECRET');
$username = $request->input('username');
$password = $request->input('password');
$data = array(
'grant_type' => 'password',
'client_id' => $core_client_id,
'client_secret' => $core_client_secret,
'username' => $username,
'password' => $password,
'scope' => '',
);
return $this->performRequest('POST','/oauth/token', $data);
}
I searched on the internet a bit and found out that OAuth2 specification for header Content-Type is "application/x-www-form-urlencoded" . To fix your problem simply remove 'content-type' => 'multipart/form-data' from 'headers'
Here is a complete code
$core_client_id = env('CORE_CLIENT_ID');
$core_client_secret = env('CORE_CLIENT_SECRET');
$username = $request->input('username');
$password = $request->input('password');
return $this->performRequest('POST','/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => $core_client_id,
'client_secret' => $core_client_secret,
'username' => $username,
'password' => $password,
'scope' => '',
],
]);

laravel testing fails when testing passport oauth2 with virtual database

I'm using virtual test database for my testing. my API is working with postman. but creating problem when writing test. when I execute the test it shows a long list of error containing the following message below-
"message": "Client error: POST http://localhost/oauth/token resulted
in a 401 Unauthorized
response:\n{\"error\":\"invalid_client\",\"message\":\"Client
authentication failed\"}
here is my route-
Route::post('/v1/create', 'API\v1\UserController#register');
here is my controller
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:6', 'confirmed'],
]);
if ($validator->fails()) {
return response()->json(['error'=>$validator->errors()], 401);
}
$input = $request->all();
$input['password'] = bcrypt($input['password']);
$user = User::create($input);
$http=new Client;
$response=$http->post(url("/oauth/token"),[
'form_params'=>[
'grant_type' =>'password',
'client_id' =>$request->client_id,
'client_secret' =>$request->client_secret,
'password' =>$request->password,
'username' =>$request->email,
'scope' =>''
]
]);
// return response()->json(['success'=>$response], $this->successStatus);
return $response;
}
and here is my test-
<?php
namespace Tests\Feature\API\v1;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use PharIo\Manifest\Email;
class UserTest extends TestCase
{
use WithFaker;
use DatabaseMigrations;
public $mockConsoleOutput = false;
/** #test */
public function a_user_can_create_user(){
$user= factory(User::class)->create(); //create user
$login=$this->actingAs($user,'api'); //user login with api
//create password grant client
$this->artisan('passport:client', ['--password' =>true, '--no-interaction' => true, '--redirect_uri'=>'http://localhost', '--name'=>'test client']);
// fetch client for id and secret
$client = \DB::table('oauth_clients')->where('password_client', 1)->first();
// dd($client->getData());
$email=$this->faker->email();
$password=$this->faker->password;
$newUser=$this->json('POST','/api/v1/create',[
'grant_type' =>'password',
'client_id' => $client->id,
'client_secret' => $client->secret,
'name' => $this->faker->name(),
'email' => $email,
'password' => $password,
'password_confirmation' => $password,
'remember_token' => str_random(10),
]);
// ->assertJsonStructure(['access_token', 'refresh_token']);
dd($newUser);
// $this->assertDatabaseHas('users',['email'=>$email]);
// $newUser->assertJsonFragment(['token_type'=>'Bearer']);
}
}
please help me what am I missing
I've solved it by proxying the request. it was happening because- when I was calling the "oauth/token" endpoint with guzzle- that call was treated as real call and test was not working there. so this helped me a lot to solve the problem.
$tokenRequest = Request::create('oauth/token', 'POST',$request->toArray());
$response = Route::dispatch($tokenRequest);

how can i get scope description in laravel passport

Using laravel passport for token base authentication. i have set up scope for
access token and now on controller i wanted to get the scope value and its description.
protected function authenticate(Request $request)
{
$request->request->add([
'username' => $request->username,
'password' => $request->password,
'grant_type' => 'password',
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'scope' => 'admin'
]);
$proxy = Request::create(
'oauth/token',
'POST'
);
$data = Route::dispatch($proxy);
//$data = json_decode($data);
return $data;
}
late to the party (I was looking this up myself) but check out the Passport::tokensCan array. You can define scopes and scope descriptions in there.
https://laravel.com/docs/5.8/passport#defining-scopes

How I can authenticate in codeception functional tests using Sentinel?

I am trying to test laravel app using codeception.
Inside Laravel I am using Sentinel for authentication.
I have a problem to authenticate user inside tests.
Non of the following examples works:
$I->authenticateTestUser($I);
$user = \App\User::where('email','=','test#test.com')->first();
$I->amLoggedAs($user);
$I->canSeeAuthentication();
$credentials = [
'email' => 'test#test.com',
'password' => '****',
];
Sentinel::authenticate($credentials, true);
$I->canSeeAuthentication();
$I->amOnPage('/login');
$I->fillField('email', $email);
$I->fillField('password', $password);
$I->click('Login');
$I->canSeeAuthentication();
How I can authenticate in codeception functional tests?
You just have to use sentinel in the _before method :
private $_user;
private $_credentials;
public function _before(FunctionalTester $I)
{
// we create a user
$this->_credentials = [
'last_name' => 'Test',
'first_name' => 'test',
'email' => 'test#test.fr',
'password' => 'password',
];
$this->_user = \Sentinel::register($this->_credentials, true);
// we log this user
\Sentinel::authenticate($this->_credentials);
}
And you will be able to reuse your user or your credentials in your tests.

Resources