How to limit user actions with Laravel Passport Scopes + Password Grant Type

4.5k views Asked by At

I have set up the Laravel Passport package for Laravel 5.3 just as described in the official documentation (https://laravel.com/docs/5.3/passport#introduction).

I want the API to be consumed by a mobile application, so I am trying to implement Password Grant Tokens. I have created a password grant client, and the token request process...

$response = $http->post('http://my-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => '[email protected]',
        'password' => 'my-password',
        'scope' => '',
    ],
]);

...Just works as expected, returning an access-token and a refresh-token for one of my users.

But now I want to define some scopes so I can limit the access of users... Following the documentation again, I have them defined in boot method of AuthServiceProvider.php like:

Passport::tokensCan([
    'admin' => 'Perform every action',
    'user' => 'Perform only normal user actions',
]);

In this scenario, if a "malicious" normal user requested a token (using the above POST call) specifying 'scope' => 'admin', he or she would get an 'admin' token... and that is not what I want.

Thus, I would like to know how is the workflow in this situation to effectively limit the access to normal users, and where do I have to implement the scope validation logic.

Thanks in advance.

3

There are 3 answers

0
Kingsley On

I forget where I read it, some Github issues somewhere, but apparently Laravel doesn't have that ability built in, each client is the treated the same equally, out of the box.

A user provided a good solution, and I built upon it here: https://stackoverflow.com/a/55285483/1132557

0
georaldc On

In my opinion, I think what confuses most people with OAuth and APIs is that scopes are tied to "clients" and not the "resource owner" themselves. Clients should be able to talk to an API using an admin scope or no scopes at all if needed. If they use an admin-ish type scope together with user context (password grant, authorization code grant, etc), then there is no stopping them from making calls that require such a scope against that user in the API. To me, the only person that can truly be classified as malicious would be one who manages to steal an access token containing an admin scope. That is why API implementors are allowed to specify what scopes to grant a client and if it's a first party app that uses something like the Password Grant, then you as a user has no choice but to trust it with your data.

I don't know how one would do this and use the retrieved token inside another's mobile app but if you did try requesting a token manually yourself with an admin scope, then I really don't see anything wrong that (other than you giving the app more control with you set as user context, so it may even be counter productive?)

If you need more control than that, then you need to go past your API and create something like application-level permissions for each user inside your resource server.

0
Mauricio Trajano On

One way to go about this would be to create a middleware

For example if you only want users with an email from example.com to request the admin domain you can do something like this

Example ScopeLogic.php middleware:

if ($request->input('grant_type') === 'password') {
    $scope = $request->input('scope');
    $username = $request->input('username');

    if ($scope === 'admin' && (strpos($username, '@example.com') === false)) {
        return response()->json(['message' => "Not authorized to request admin scope"], 401);
    }
}

return $next($request);

Of course, you would have to add this scope to your $routeMiddleware array in Kernel.php

protected $routeMiddleware = [
    ...
    'check-scopes' => \App\Http\Middleware\ScopeLogic::class
]

As well as wrap Passport::routes() in AuthServiceProvider.php to check for this middleware

\Route::group(['middleware' => 'check-scopes'], function() {
    Passport::routes();
});

Passport will also check that a correct username and passport combination was passed so you don't have to worry about that in the middleware