Lavarel Auth session lost after request - laravel

We try to authenticate a user manually via the builtin Auth functionality.
We don't retain a database as that's not needed for our purpose.
We followed this part of the docs: https://laravel.com/docs/9.x/authentication#authenticate-a-user-instance
We make a user on a certain request, we checked the Auth::check() method and I get a true, so it's working:
$user = User::make([
'name' => $userGoogle->name,
'email' => $userGoogle->email,
'token' => $userGoogle->token,
'refresh_token' => $userGoogle->refreshToken,
'employeeId' => $employeeId,
]);
Auth::login($user);
//request()->session()->invalidate(); //Needed?
//request()->session()->regenerateToken(); //Needed?
dd(Auth::check());
But, when loading another test page:
Route::get('test', function() {
dd(Auth::check());
});
This always returns false. I also tried with regenerating the session token as I suspect something is going wrong there.
But no avail. It just doesn't work. Or is a database mandatory? It's not stated in the docs, and the ::check() after Auth::login works perfectly.
Thanks

Related

Laravel passport generate user access and refresh token with only user id

Currently i would like to generate user access and refresh token after registering a user inorder to automatically login a user after registrations
Currently am doing this.
$user = User::create(//user detaisl) //this creates a user
$guzzle = new \GuzzleHttp\Client([
'base_uri' => env("APP_URL"),
'defaults' => [
'exceptions' => false
]
]);
$response = $guzzle->post('/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => env("PASSPORT_CLIENT_ID"),
'client_secret' => env("PASSPORT_CLIENT_SECRET"),
'username' => $user->email,
'password' => $request->input("password"),
],
]);
return json_decode((string) $response->getBody(), true);
The above with guzzle works but i was wondering if there is a simpler way to simply generate access and refresh tokens without need to perform another guzzle http request by simply using the user id after create.
I would like this as guzzle sometimes fails to work especially on localhost during development continously hangs.
Is there another way?
Instead of using a guzzle request, you can call directly the controller method that handles the token route. Generally directly calling another controller method is a bit of a code smell. You could attempt to dive into the code to refactor this out if you wanted, but since you don't "own" the passport code, I wouldn't worry about it.
// Save off the original request object.
$originalRequest = app('request');
// Create a new request object for the token request.
$tokenRequest = \Illuminate\Http\Request::create('/oauth/token', 'POST', [
'grant_type' => 'password',
'client_id' => config('passport.password_client_id'),
'client_secret' => config('passport.password_client_secret'),
'username' => $user->email,
'password' => $request->input("password"),
]);
// Replace the current request with the new token request in the app container.
app()->instance('request', $tokenRequest);
// Call the access token controller method using the app container,
// which will auto inject the new request.
$response = app()->call('\Laravel\Passport\Http\Controllers\AccessTokenController#issueToken');
// Replace the token request in the container with the original request.
app()->instance('request', $originalRequest);
return $response;
A couple notes:
The $user->createToken() method creates personal access tokens, not password grant tokens. Personal access tokens cannot be refreshed.
I converted the env() calls to config() calls. You should avoid using the env() method outside of the config files. As soon as you cache your config, the env() calls will return null (for values only set in the .env file).

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

How to include a CSRF Token in get route in Laravel tests?

To prevent CSRF attacks, I'm adding a state parameter with my current CSRF token to an external link. When the external page redirects the user back to my site, it will include the state.
With a simple condition I can check it in my controller:
if($data['state'] !== csrf_token()){
throw new TokenMismatchException;
}
Now I want to write a test for it to pass the if condition, but csrf_token() is empty in the test directory, however not in the controller. So it fails, unfortunately. Here is an excerpt of the test:
/** #test */
public function it_saves_the_data_in_database()
{
$this->get(route('connect.index', [
'scope' => $this->scope,
'code' => $this->code,
'state' => csrf_token(), //it's null here...
]))->assertStatus(200); //however it returns 419
}
Is it possible to generate the token before?
If you wrote HTTP access testing code before you use crsf_token() like below example.
The csrf token seems to be generated in that test session.
$this->get(...)
$this->get(route('connect.index', [
'scope' => $this->scope,
'code' => $this->code,
'state' => csrf_token(), //it's null here...
]))->assertStatus(200);

Return additional column if user is authorized - API

I have an Laravel-base API which handles both client and admin endpoints (there are two sites like domain.com and admin.domain.com).
My auth is based on cookie, which domain is <.domain.com>. As you can see, this cookie is acceptable for both domains.
I use Eloquent Api Resources for transformation data layer. Is my when() route check here safe and right?
public function toArray($request)
{
return [
'name' => $this->name,
'created_at' => (string)$this->created_at,
'status' => $this->when($request->route()->getName() === 'api.admin.users.index', $this->status)
];
}
Before I used $this->when(Auth::check(), ...), but because my auth cookie is acceptable for client site too, unneeded data might be fetched.
My route:
Route::group(['prefix' => 'admin', 'as' => 'api.admin.', 'middleware' => 'auth:api'], function () {
Route::resource('users', ...);
});
If user is not authorized, he wouldn't get data because of middleware. At the same time, authorized used (who has non-expired cookie) wouldn't get unneded data while being on client site.
Thank you!
I think your approach is fine. The route name is something internal and the user cannot tinker with it. You could improve it by using \Route::is('api.admin.*') though. It would then work for all of your admin API routes.

API login from android app using laravel 5.3 passport

For two days I am digging google but could not find the starting thread for my problem, now I am out of option. Please help me with some direction/howTo
I have a web application running built with laravel 5.3, I have installed passport as described here . if I go /home its showing perfectly.
Now I have to make an android app from which
An already existing user of web app can login
get all the task list of that user TaskModel (ons_tasks(id, title, description))
routes related only
in web.php
Auth::routes();
in api.php
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:api');
Route::group(['middleware' => ['auth:api']], function () {
Route::get('/test', function (Request $request) {
return response()->json(['name' => 'test']);
});
Route::get('/task/list', function (Request $request) {
$list = \App\Model\TaskModel::all();
return response()->json($list);
});
});
To login : if I send post request /login with email & password get the TokenMismatchException error but Where do I obtain a token for
android app in mobile? Do I need the Auth::routes() in the api too?
if then what else Do I need to just login and get a token so later I
can send it for getting the task lists.
Secondly,
If I go to /api/test it redirects me to /home page without
showing any error !!!
Thanks in advance.
To authenticate with your Passport-enabled API
You'll need to use the Password Grant Client in this situation, see this section of Passport's documentation.
Once you've generated a Password Grant Client, using:
php artisan passport:client --password
You will need to request an access token from your application, and send it with your subsequent requests, in order to access the protected auth:api middleware routes.
To get an access token, send a request to your app's /oauth/token route (this is a PHP implementation obviously, ensure you are correctly formatting below request in your Java implementation):
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => '<client id returned from the artisan command above>',
' client_secret' => '<secret returned from artisan command above>',
'username' => 'taylor#laravel.com',
'password' => 'my-password',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
Ensure you add the client_secret and client_id that was returned from the artisan call above, and ensure username and password references a valid user in your database.
If everything is fine here, you should receive an access_token and refresh_token in the response. The access_token is what you need to authenticate using the auth:api guard. To correctly pass this back to your api, you will need to send your subsequent requests with the headers Authorization: Bearer <your accessToken> and Accept: application/json
For example, to access your "test" route:
$response = $client->request('GET', '/api/test', [
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '. <accessToken from /oauth/token call>,
],
]);
If you've set these correctly, you should see a JSON response with the array you have specified.
Why is /api/test redirecting me with no error?
You are requesting a route with the auth:api middleware. This will redirect you as you have not specified the correct headers as described above, this is expected behavior.
Hope this helps.

Resources