Laravel: Why am I getting a 401 response from Passport in my feature test? - laravel

I am unsuccessfully trying to test authentication using Passport.
Here is my feature test:
public function a_user_can_authenticate_using_email() {
// Install passport.
$this->artisan('passport:install', ['--no-interaction' => true]);
// Create a user.
$user = factory(User::class)->create([
'username' => 'test#example.com',
'password' => 'password',
]);
// Get the id & secret.
$client = DB::table('oauth_clients')
->where('password_client', true)
->first();
// dd('id: ' . $client->id); // works great
// dd('secret: ' . $client->secret); // works great
// dd('username: ' . $credentials['username']); // works great
// dd('password: ' . $credentials['password']); // works great
$response = Http::asForm()->post(env('APP_URL') . '/oauth/token', [
'grant_type' => 'password',
'client_id' => $client->id,
'client_secret' => $client->secret,
'username' => $credentials['username'],
'password' => $credentials['password'],
'scope' => '*',
]);
dd($response->status()); // 401
}
If I dump the response body, I am seeing an invalid client:
dd($response->body());
"{"error":"invalid_client","error_description":"Client authentication failed","message":"Client authentication failed"}"
I don't understand why I am seeing this error in my test.
If I use Postman to test my application, everything works great so I'm confident this is something specific to my test.
I am using sqlite memory database.
If I hardcode these values in my .env or just right in the test block, everything works great as well. How can I properly get the values from the database in my test? Thank you for any suggestions!

You'll have to run $user->save() so the user is saved to the database before using it to log in.

Related

How to test Auth::once() in Laravel?

I'm using Auth:once() to authenticate users for a single request.
if (!Auth::once($this->only('email', 'password'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
// we store the user + password for one request in the session
// so we can use it in the next request
$this->session()->put('email', $this->email);
$this->session()->put('password', $this->password);
But the test user is permanently authenticated, and assertGuest() is always false, not just for a single request. This is happening only in the test!
$user = User::factory()->create();
$response = $this->post('/api/login', [
'email' => $user->email,
'password' =>' password',
]);
$response = $this->get('/api/home');
$this->assertGuest();
I tested manually from Postman, and everything seems to be okay. So I think Laravel considers it a single request for the entire test.

Making an OAuth request with Laravel for Stripe

I was translating following PHP code:
$person = \Stripe\Account::createPerson(
'{{CONNECTED_ACCOUNT_ID}}', // id of the account created earlier
[
'person_token' => $token,
]);
To:
$making_user = $stripe->account()->persons()->create(
$request[0], // id of the account created earlier
[
'person_token' => $request[1],
]);
Above Laravel code works find without any issue. Does anyone have any idea that what can be the equivalent Laravel syntax of the following:
$response = \Stripe\OAuth::token([
'grant_type' => 'authorization_code',
'code' => 'ac_123456789',
]);
I'm using following as equivalent, but it is giving me error of invalid method "oauth"
$making_account = $stripe->oauth()->create([
'grant_type' => $request['code'],
'code' => 'ac_123456789',
]);
I'm not finding anywhere its syntax anyone have an idea what will be the Laravel syntax of making Oauth call?
This worked for me.
$stripe = new \Stripe\StripeClient('SECRET_KEY_HERE');
$response = $stripe->oauth->token([
'grant_type' => 'authorization_code',
'code' => 'CODE_HERE'
]);

Laravel Passport: How can I properly setup a feature test?

I am trying to create a Feature test that tests getting an OAuth access token. When using Postman, everything works great (actual code) so I am confident it is in my test code I am not implementing the setup correctly.
In the past, I have run the artisan command, and simply copy/paste the client_secret into my .env file. Because I am using CI/CD, I cannot do that. I need to either:
create an artisan command to append the .env
read the secret from the oauth_clients table (seems the easiest route)
I've followed this SO thread to help get where I'm at, but not able to make it work.
Here is what my test looks like:
MyFeatureTest.php
use RefreshDatabase, WithFaker, DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
$this->artisan('passport:install');
}
public function a_user_can_get_a_token()
{
$credentials = [
'username' => 'username',
'password' => 'password',
];
$http = $this->postJson('api/v1/login', $credentials);
$response = json_decode($http->getContent());
// dd($response);
$http->assertStatus(200)
->assertJsonStructure([
'token_type', 'expires_in', 'access_token', 'refresh_token',
])
->assertJson([
'token_type' => $response->token_type,
'expires_in' => $response->expires_in,
'access_token' => $response->access_token,
'refresh_token' => $response->refresh_token,
]);
In my Controller that handles the login route:
public function __construct()
{
$this->client = DB::table('oauth_clients')
->where('id', 2)
->first();
}
...
public function getOauthAccessToken($credentials)
{
$response = Http::asForm()->post(env('APP_URL') . '/oauth/token', [
'grant_type' => 'password',
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'username' => $credentials['username'],
'password' => $credentials['password'],
'scope' => '*',
]);
if ($response->successful()) {
return $response->json();
} else {
dd($response->body());
}
}
When using postman to test my routes/controller, everything works great. I get a Barer token back as expected.
When I run my Feature test, my controller returns null.
Trying to troubleshoot, I've tried dd($response->body()) above. Here is what I am getting:
"{"error":"invalid_client","error_description":"Client authentication failed","message":"Client authentication failed"}"
If I dd($this->client->secret), I am able to see the key. This is super confusing as it looks like everything is working...
How can I properly set up my test so that passport is ready to go/configured when I hit the login test(s)? Thank you for any suggestions!
Thank you everyone for your suggestions!
This is what ended up working for me. Since I am the only consumer of my api, I don't need to return the entire /oauth/token response (although it might be helpful in the future).
MyTest.php
use RefreshDatabase, WithFaker, DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
$this->artisan('passport:install', ['--no-interaction' => true,]);
}
/** #test */
public function a_user_can_authenticate_using_email()
{
$credentials = [
'username' => 'test#email.com',
'password' => 'password',
];
$http = $this->postJson('api/v1/login', $credentials)
->assertStatus(200);
}
MyController.php
// authenticate the $credentials to get a $user object.
...
return $this->getOauthAccessToken($user);
...
private function getOauthAccessToken($user)
{
$token = $user->createToken('My Personal Access Token')->accessToken;
return response()->json($token);
}
Instead of validating I get the exact json back (would be ideal) I'm just verifying that I'm getting a 200 response back. Because my authentication is somewhat cumbersome (checking multiple db's) it was important to make sure that I was getting through everything with a 200.
Hope this helps someone!

How to test login with Dusk on a Laravel/Lighthouse/Passport/Nuxt App?

I've setup a Nuxt app with Laravel, and before doing too much I would like to setup Dusk testing. The app/login works great but I can't make it work on automated Dusk tests.
It use lighthouse-graphql-passport-auth with the setup described here. I think the issue is probably with the token generation in the Dusk environment.
Here is the example test which I can't make work:
public function testWithLoggedIn()
{
$user = factory(User::class)->create([
'email' => 'test#dusk.com',
'password' => bcrypt('test'),
]);
$this->browse(function ($browser) use ($user) {
$browser->visit('/login')
->type('username', $user->email)
->type('password', 'test')
->press('Login')
->assertSee('Hello First User');
});
}
So I already tried a few things. For example if I add this in the setUp() method:
$this->artisan('passport:install');
$client = Client::findOrFail(2);
config()->set('lighthouse-graphql-passport.client_id', 2);
config()->set('lighthouse-graphql-passport.client_secret', $client->secret);
I get this error:
Error: GraphQL error: Client authentication failed
I also tried to add the same informations as I have locally for the oauth_clients. Unfortunately the redirect is different ('http://local.test' for local, and 'http://apache' when testing in dusk).
DB::table('oauth_clients')->insert([
'id' => 2,
'name' => 'Apps Password Grant Client',
'secret' => 'SWjOP5Y8NzxEx7z3dlaVs08VDUWgrSl96l4V9JO3',
'redirect' => 'http://apache',
'personal_access_client' => 0,
'password_client' => 1,
'revoked' => 0,
'created_at' => '2020-02-17 11:02:33',
'updated_at' => '2020-02-17 11:02:33',
]);
This returns this kind of error:
Error: GraphQL error: The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
How should I test this application? Everything will be behind this login screen so there is probably a way, and if needed I can change the login method (maybe without using passport).
It turns out the second solution with the DB::table insertion worked.
The missing thing was to wait a little in the test before the assert (with an ->pause(300) or a ->waitFor('.selector', 0.2).
For example this worked:
$this->browse(function ($browser) use ($user) {
$browser->visit('/login')
->type('username', $user->email)
->type('password', 'test')
->press('Login')
->waitFor('.admin-dashboard', 0.2)
->assertSee('Welcome');
});

Laravel tests are not saving factories to database

I'm having trouble with testing in that the records of my factories are not being saved to the database for my API tests. Here is an example test
use DatabaseTransactions;
public function testUserMe()
{
$user = factory(\App\Models\User::class, 'userA')->create();
$token = factory(\App\Models\ApiToken::class, 'userA-token')->create();
$this->refreshApplication(); // I've tried with and without this line
$headers = ['HTTP_Authorization' => 'Bearer '. $token->token];
$this->json('GET', '/user/me', [], $headers, $headers)
->assertJson([
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'email' => $user->email,
])
->assertStatus(200);
}
Note: I have $headers in the request twice as I'm seeing inconsistencies in examples but this is irrelevant to the issues.
The issue I'm having is that the middleware is failing as the user and token have not been committed to the database. If I remove the use DatabaseTransactions it will work as expected (middleware works and tests pass) but the data is not cleared after the test.
I have the exact same thing working on a previous project with what I think is the same setup, including the lack of use of setup() and teardown(). Does anyone have some ideas on where else I can look?
Why would use DatabaseTransactions be preventing the records being created?

Resources