1

I'm currently building a SPA type of application prototype. The first step is to implement an API with Laravel Passport and secure it. For that, I'm taking inspiration of this existing structure : Laravel SPA. The problem is that, none of the API URL's are protected which means, as a user, I can request all informations from the API.

So I decided to start from scratch with more security. I'm using a role and permission package which is : Laravel-permission.

This is the first time I'm implementing and API and I was stuck on the concept of scopes with Laravel passport, because they can be add directly in the API request, without a checking based on the role of the user.

I found someone who gave a solution on StackOverflow which can be found here : Role based API url protection.

So here is my implementation :

// AuthServiceProvider
public function boot()
{
    $this->registerPolicies();

    // We define all the scopes for the tokens
    Passport::tokensCan([
        'manage-continents' => 'Manage continents scope',
        'read-only-continents' => 'Read only continents scope',
    ]);

    Passport::routes();
}

Then, I'm creating a REST Controller with Laravel ressources controller.

// Rest Controller

namespace App\Http\Controllers\API\GeoLocation;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

use App\Models\GeoLocation\Continent as Continent;

class ContinentController extends Controller
{
    public function index()
    {
        // allow listing all continents only for token with manage continent scope
        return Continent::all();
    }

    public function store(Request $request)
    {
        // allow storing a newly created continent in storage for token with manage continent scope
    }


    public function show($id)
    {
        // allow displaying the continent for token with both manage and read only scope
    }
}

Then in the api.php file, I'm adding the following routes :

Route::get('/continents', 'API\GeoLocation\ContinentController@index')
    ->middleware(['auth:api', 'scopes:manage-continents']);

Route::post('/continents', 'API\GeoLocation\ContinentController@store')
    ->middleware(['auth:api', 'scopes:manage-continents']);

Route::get('/continents/{id}', 'API\GeoLocation\ContinentController@show')
    ->middleware(['auth:api', 'scopes:manage-continents, read-only-continents']);

I'm then creating a Controller to intercept the request, and to add scopes based on the user role. Problem, I think this method is never reached as I'll explain after.

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

class ApiLoginController extends Controller
{
    use AuthenticatesUsers;

    protected function authenticated(Request $request, $user)
    {   
        // Implement your user role retrieval logic
        // for example retrieve from `roles` database table
        $roles = $user->getRoleNames();

        $request->request->add(['username' => $request->email]); 

        // @TODO to avoid many requests, we should just deepdive into
        // the collection returned by the role

        // grant scopes based on the role that we get previously
        if ($roles->contains('hyvefive_super_administrator')) 
        {
            // grant manage order scope for user with admin role
            $request->request->add([
                'scope' => 'manage-continents'
            ]);
        } 
        else 
        {
            // read-only order scope for other user role
            $request->request->add([
                'scope' => 'read-only-continents'
            ]);
        }

        // forward the request to the oauth token request endpoint
        $tokenRequest = Request::create(
            '/oauth/token',
            'post'
        );

        return Route::dispatch($tokenRequest);
    }
}

The last step before testing everything is to add the route of that controller in the api.php file :

Route::post('login', 'Auth\ApiLoginController@login');

Finally, to test, I'm just using the HomeController from the Auth default Laravel package as following :

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use GuzzleHttp\Client;
use Auth;

class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        // from client application
        $http = new Client();

        $response = $http->post('http://hyvefivity.local/api/login', [
            'form_params' => [
                'grant_type'    => 'password',
                'client_id'     =>  4,
                'client_secret' => 'fsW4E5fcQC0TGeVHOrvr1qlZ8TEgrgpSRziVLCDS',
                'username'      => 'myemail@gmail.com',
                'password'      => 'my-secret-password',
                'scope'         => 'manage-continents',
            ],
        ]);

        // You'd typically save this payload in the session
        $auth = json_decode((string) $response->getBody(), true);

        var_dump($auth);

        /*$response = $http->get('http://hyvefivity.local/api/continents', [
            'headers' => [
                'Authorization' => 'Bearer '.$auth->access_token,
            ]
        ]);

        $continents = json_decode( (string) $response->getBody() );
*/
        // return view('home');
    }
}

The problem is that I feel the ApiLoginController is never reached, and same goes for the authenticated method.

If I'm doing the following :

$http = new Client();

$response = $http->post('http://hyvefivity.local/oauth/token', [
    'form_params' => [
        'grant_type'    => 'password',
        'client_id'     =>  4,
        'client_secret' => 'fsW4E5fcQC0TGeVHOrvr1qlZ8TEgrgpSRziVLCDS',
        'username'      => 'my-email@gmail.com',
        'password'      => 'my-seret-password',
        'scope'         => 'manage-continents',
    ],
]);

A token is generated, but not with the scopes I've added in the ApiLoginController.

Another improvement I would like to do in addition is : should I do that API call during the login because, if I'm doing it in the HomeController, the problem is that the password is hashed, which means it's impossible to ask for a Password Grant type of token without having the password during the login ?

halfer
  • 19,824
  • 17
  • 99
  • 186
Zero
  • 443
  • 1
  • 7
  • 23
  • I'm not clear on whether you have your objective entirely clear. May I ask, do you wish to have an api which allows access only to authenticated users, and also to know which authenticated user is currently accessing the api, e.g. in just the same way that the regular laravel auth system does? If your answer to that is "yes, that's all I need" then maybe I can help you. – mwal Mar 10 '18 at 15:12
  • Sorry if my objective is not clear enough. My goal is to have an API which is going to be consumed by a website and a mobile application. It has to allows for now only access to authenticated users indeed. My goal is to restraint API URLS based on the role of authenticated users just like described [here](https://stackoverflow.com/questions/39436509/laravel-passport-scopes/40433000#40433000) – Zero Mar 10 '18 at 15:16
  • All I can really add is that I'm using laravel passport but only with the [JWT sync token](https://laravel.com/docs/5.6/passport#consuming-your-api-with-javascript) which means none of oauth table data is needed at all. It took me a while to figure out that a whole bunch of what passport provides was actually not needed for my case! since all I need is an authenticated api to be consumed by my laravel app. If you definitely need to access it authenticated from a non-laravel app, then it looks like your needs may be greater than mine. – mwal Mar 10 '18 at 15:36
  • @Zero I'm looking for a solution to this..Did you find one? I need role-based authentication + authorization in Laravel Passport. – kp123 Aug 22 '19 at 21:02
  • I am also looking for solution to this. If you find any solution do let me know. – hemant Aug 13 '20 at 14:23

1 Answers1

-1

Passport provide it as default but it is also not working propely

Passport::actingAs($user,['scope']);



public static function actingAs($user, $scopes = [], $guard = 'api')
hemant
  • 156
  • 1
  • 6