how can i get scope description in laravel passport - laravel

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

Related

How to generate Passport oauth_clients for testing database?

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);

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 cors throws error when trying to run Request::create

I have a custom login method for my Laravel Passport API.
I am using Request::create to call the /oauth/token endpoint from inside my login method.
The problem is that from within my React project, I am getting a CORS error.
The error ONLY happens when using the login method and I think I have traced it down to the way that the data is being 'returned'
I am using the barryvdh/laravel-cors package and the relevant portion of my login method is as follows;
$data = [
'grant_type' => 'password',
'client_id' => 2,
'client_secret' => 'ggdfgdvsreckuscenusekubsvbd',
'username' => $request->email,
'password' => $request->password,
'scope' => '',
];
$request = Request::create('/oauth/token', 'POST', $data);
return app()->handle($request);
Is there a way that I can cast that $request response to a variable and then return it using the standard response->json?
If i return the $request as it is, it doesnt give me any of the data that the return app()->handle($request); line gives me i.e token, refresh token etc etc
I can use all other POST methods in my API except this one.
You can try it this way. Its basically launching a new request from a another request
$data = [
'grant_type' => 'password',
'client_id' => 2,
'client_secret' => 'ggdfgdvsreckuscenusekubsvbd',
'username' => $request->email,
'password' => $request->password,
'scope' => '',
];
$request = app()->make('request');
$request->request->add($data);
$tokenRequest = Request::create(
env('APP_URL') . '/oauth/token',
'post'
);
return Route::dispatch($tokenRequest);
Dispatch the route and handle the response internally instead of returning different headers
protected function attemptLogin(Request $request)
{
// forward the request to the oauth token request endpoint
$res = Route::dispatch(request()->create('oauth/token', 'POST', $data));
// Return true or false based on response status code
return $res->getStatusCode() === 200 ? true : false;
}

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);

Resources