Laravel Gates, how to use them in the API route? - laravel

I'm defining my Gates for my API service in AuthServiceProvider (following the Laravel docs https://laravel.com/docs/7.x/authorization#gates):
Gate::define('view-users', function ($user) {
return $user->hasAccess(['view-users'])
or $user->inRole(['admin', 'operator']);
});
Here is my route:
Route::group(['namespace' => 'Api', 'middleware' => ['auth:api']], function () {
Route::get('/users', 'UserController#listing')->name('user.listing')->middleware(['can:view-users']);
});
How can I find the get the user from the Route API file to use in the Gate?
But I don't exactly understand where this $user is coming from. In my request I am sending an Authorization Bearer token. Should I be using this within my gate to fetch the correct user from the DB? How does Laravel know who $user is?

The $user is the current logged in user. You do not need to provide the additional $user, Or pass the user in.
So if your app currently has a login user, that will be the one. If there is no login user the gate will return false which is protecting your resources.

You'll notice from the documentation that Laravel provides the User for you in a Gate.
It says (emphasis mine):
Gates are Closures that determine if a user is authorized to perform a given action and are typically defined in the App\Providers\AuthServiceProvider class using the Gate facade. Gates always receive a user instance as their first argument.
As Andy Song has pointed out in the comments, Laravel will resolve the User via Auth. If there is no user (not logged in, or no authentication), then, per the docs:
By default, all gates and policies automatically return false if the incoming HTTP request was not initiated by an authenticated user.
If you want to trace how your user gets authenticated, your code snippet defines this middleware:
'middleware' => ['auth:api']
This uses the auth middleware, with api as a parameter. Your middleware is defined in app/Http/Kernel.php, and assuming you're using stock Laravel, it will then go on to authenticate your user, prior to your gate check taking place.

Laravel doesn't know who the $user is. You need to pass it when you use the Gate. If you pass Auth::user() as a first argument of the Gate you are using, the code you wrote will work. In other cases, you will need to fetch the user from the Database with any given input.

Related

Auth::check() for client credential workflow with Passport

I have a Laravel app where some routes are protected with the 'client' middleware from Laravel Passport (Example from the docs):
Route::get('/orders', function (Request $request) {
...
})->middleware('client');
When I need to explicitly check for the success of authentication in my code, I normally do this with:
\Illuminate\Support\Facades\Auth::check();
However, this function appears to always return false with the client credentials workflow. I understand that e.g. Auth::user() does not make sense in this context but Auth::check() should be well-defined in this situation.
Question: Is there an alternative to Auth::check() for the client credentials workflow or am I missing something?
You need to pass the Access Token in header first, after doing so you should be able to check auth like this:
Auth::guard('api')->check()

Laravel route in multiple middelwares

I want to have the same route within the auth:api middleware, and also out of it.
Right now, if I include it in both, only the one out of the auth:api is taken into consideration, even if the user is logged in.
Is there a way that if the user is logged in it goes to auth:api and if not it goes out of any middleware?
The reason to do this is that if the user is logged in, I want to access user information, and for that it needs to go through the auth:api.
As long as you're including the token in the request, you will be able to get access to the current User.
Out-of-the-box, Laravel will set the default guard to be web. When you place routes under the auth middleware it will set the default guard to be whatever is passed to the middleware i.e. when you have auth:api it will set the default guard to be api for that request.
If you want to be able to access the User without it being under the auth:api middleware, you will simply need to be explicit with what guard should be used e.g.
auth('api')->user(); // or Auth::guard('api')->user();
The same applies for check():
auth('api')->check(); // or Auth::guard('api')->check();
or if you're using the Request object:
$request->user('api'); // or request()->user('api');
It 's not possible to have multiple same routes in your application and work independently. Laravel will match the first one that it find in your routes map.
Create one route and check for authentication in your controller.
if (Auth::check()) {
// The user is logged in...
}

Using client credentials middleware for all API requests

In my routes/api.php file, I have a route group like this:
Route::group([
'prefix' => config('api.route_prefix'),
'middleware' => ['api', 'auth:api'],
], function() {
// ...
This correctly only allows users with tokens retrieved via password grant access to those routes. When trying to implement client credentials grant, I found that a separate middleware is necessary. Since the auth:api middleware raises an exception, this presents a conflict because I want requests with valid tokens of either grant type to access these routes.
What I found is that using just the client credential middleware seems to validate both, but I am unsure if there are any bad implications of doing so.
Is there anything wrong with circumventing the auth:api middleware and replacing it with Laravel\Passport\Http\Middleware\CheckClientCredentials?
One apparent big downside is that the client credentials doesn't seem to have any user information in the JWT token. This causes the user resolver for the request to return null for calls to request()->user(). From Laravel\Passport\Guards\TokenGuard::authenticateViaBearerToken, this was returning null:
// If the access token is valid we will retrieve the user according to the user ID
// associated with the token. We will use the provider implementation which may
// be used to retrieve users from Eloquent. Next, we'll be ready to continue.
$user = $this->provider->retrieveById(
$psr->getAttribute('oauth_user_id')
);
Tracing $psr->getAttribute led me to League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator::validateAuthorization:
// Return the request with additional attributes
return $request
->withAttribute('oauth_access_token_id', $token->getClaim('jti'))
->withAttribute('oauth_client_id', $token->getClaim('aud'))
->withAttribute('oauth_user_id', $token->getClaim('sub'))
->withAttribute('oauth_scopes', $token->getClaim('scopes'));
All of the attributes except oauth_user_id were being set correctly via the claims on the token, $token in my case is an instance of Lcobucci\JWT\Token. So only using the client credentials middleware is not a good solution to having a single set of routes, even if using an oauth client with a specified user_id.

Laravel 5.3 Ajax Login Customize Credentials

I am able to login via Ajax in Laravel 5.3
This is easily accomplished by making a post request to the login route with the proper parameters.
However, for my application, I am designing two ways for a user to be logged in - via the traditional email/password combination that Laravel already supports, and via an access code that would be distributed and allow the possessor of said code to login without an email/password combination. There is no "registration" in my app, there is just different levels of authentication.
Anyway, in /vendor/laravel/framework/src/Illuminate/Foundation/Auth I am editing the AuthenticatesUsers.php and understand that this function specifically handles the login attempts:
protected function attemptLogin(Request $request)
{
return $this->guard()->attempt(
$this->credentials($request), $request->has('remember')
);
}
My question is, how can I change the success of attempt() based on the content of the request?
In other words, if someone is sending an ajax access code it shouldn't be tested against an email/password combination, as it would obviously fail. Similarly, if they are sending an ajax with email/password parameters, it shouldn't be tested against the list of available access codes.
Am I on the right track? Where in Laravel can I go to make the Auth::attempt() contingent on request parameters?
I will not advice to edit a framework file.
You should rather write a middleware to handle identification of the type of authentication user is requesting for, before sending it to the controller. In your middleware,
public function handle($request, Closure $next)
{
// check if the request has access_code
$request->attributes->add(['using_access_code' => $request->has('access_code')]);
return $next($request);
}
And in your controller, you can check for positive test on this request parameter that we newly added (you can of course do this inside controller directly, but I personally like middleware to handle this because there are chances that you may want to add more functionality)
In your controller, if using_access_code is false, proceed with attempt() login, else, retrieve the user using access_code and manually authenticate the user using Auth::login($user). Let me know if you are unclear.

Laravel, can't log a user by id and then redirect him

I'm using Laravel 5.2. I'd like to log a user by his id and then redirect him to the dashboard but it's not working.
I did this:
$result = Auth::loginUsingId($id);
var_dump($result->toArray());
and the result is fine. It returns the object user with all his data.
But after redirecting the user to the dashboard with return redirect()->route('dashboard'); it send me to login page!
I discover then that Auth::user() returns null !
What shall i do?
Thanks
Authentication needs sessions and for sessions to work you need to use the web middleware. So the routes that need working sessions should be defined like this:
Route::group(['middleware' => ['web']], function () {
// Routes that need sessions go here
});
Use $redirectTo as stated in the documentation, if you get into login again Auth wasn't successful, perhaps something related with session or cookies, or just a bad time configuration. Try Auth::loginUsingId($id, true); then.

Resources