Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Passport Scopes

I am a bit confused on the laravel scopes part.

I have a user model and table.

How can I assign a user the role of user, customer and/or admin.

I have a SPA with vue and laravel api backend. I use https://laravel.com/docs/5.3/passport#consuming-your-api-with-javascript

    Passport::tokensCan([         'user' => 'User',         'customer' => 'Customer',         'admin' => 'Admin',     ]); 

How can i assign which user model has which scope(s)?

Or are scopes not the same as roles?

How would you implement this?

Thanks in advance!

like image 540
Jeroen Herczeg Avatar asked Sep 11 '16 13:09

Jeroen Herczeg


People also ask

What is scope in laravel Passport?

Scopes allow your application's users to limit the actions a third-party application can perform on their behalf. Laravel passport token scope provides you beautiful services. Scopes allow your API clients to request a specific set of permissions when requesting authorization to access an account.

Does laravel Passport use JWT?

Passport uses JWT authentication as standard but also implements full OAuth 2.0 authorization.

Which is better JWT or Passport in laravel?

The "tymondesigns/jwt-auth" is a PHP Laravel implementation of the JWT protocol. On the other hand, Passport also uses JWT by default plus a huge extra, a complete Oauth2 implementation. Regarding the functionality, as I said they both use JWT thus you can use whichever you like to authentication via tokens.

What is the difference between laravel Passport and Sanctum?

Chatty Cathy @vincent15000 Passport is an OAuth server implementation, and used to offer OAuth authorisation for your application. Sanctum is an authentication library for “simpler” token-based authentication for clients that need it (i.e. mobile apps) but also offers cookie-based authentication for SPAs.


2 Answers

Or are scopes not the same as roles?

The biggest difference between the two is the context they apply to. Role-based Access Control (RBAC) governs the access control of a user when using the web application directly, while Oauth-2 scope governs the access to the API resources for an external client on behalf of a user.

How can i assign which user model has which scope(s)?

In general Oauth flow, a user (as a resource owner) is requested to authorize a client on things that it can and cannot do on his/her behalf, these are what you called scope. On successful authorization the scope being requested by the client will be assigned to the generated token not to the user per se.

Depending on which Oauth grant flow that you choose, the client should include the scope on its request. In Authorization code grant flow the scope should be included on HTTP GET query parameter when redirecting the user to authorization page, while on Password grant flow the scope must be included in HTTP POST body parameter to request a token.

How would you implement this?

This is an example with Password grant flow, with assumption that you completed the laravel/passport setup beforehand

Define scopes for both admin and user role. Be specific as you can, for example: admin can manage-order and user only read it.

// in AuthServiceProvider boot Passport::tokensCan([     'manage-order' => 'Manage order scope'     'read-only-order' => 'Read only order scope' ]); 

Prepare the REST controller

// in controller namespace App\Http\Controllers;  class OrderController extends Controller {        public function index(Request $request)     {         // allow listing all order only for token with manage order scope     }      public function store(Request $request)     {         // allow storing a newly created order in storage for token with manage order scope     }      public function show($id)     {         // allow displaying the order for token with both manage and read only scope     } } 

Assign the route with api guard and scope

// in api.php Route::get('/api/orders', 'OrderController@index')     ->middleware(['auth:api', 'scopes:manage-order']); Route::post('/api/orders', 'OrderController@store')     ->middleware(['auth:api', 'scopes:manage-order']); Route::get('/api/orders/{id}', 'OrderController@show')     ->middleware(['auth:api', 'scopes:manage-order, read-only-order']); 

And when issuing a token check the user role first and grant the scope based on that role. To achieve this, we need an extra controller that use AuthenticatesUsers trait to provide login endpoint.

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         $role = $user->checkRole();          // grant scopes based on the role that we get previously         if ($role == 'admin') {             $request->request->add([                 'scope' => 'manage-order' // grant manage order scope for user with admin role             ]);         } else {             $request->request->add([                 'scope' => 'read-only-order' // read-only order scope for other user role             ]);         }          // forward the request to the oauth token request endpoint         $tokenRequest = Request::create(             '/oauth/token',             'post'         );         return Route::dispatch($tokenRequest);     } } 

Add route for api login endpoint

//in api.php Route::group('namespace' => 'Auth', function () {     Route::post('login', 'ApiLoginController@login'); }); 

Instead of doing POST to /oauth/token route, POST to the api login endpoint that we provided before

// from client application $http = new GuzzleHttp\Client;  $response = $http->post('http://your-app.com/api/login', [     'form_params' => [         'grant_type' => 'password',         'client_id' => 'client-id',         'client_secret' => 'client-secret',         'username' => '[email protected]',         'password' => 'my-password',     ], ]);  return json_decode((string) $response->getBody(), true); 

Upon successful authorization, an access_token and a refresh_token based on scope that we define before will be issued for the client application. Keep that somewhere and include the token to the HTTP header whenever making a request to the API.

// from client application $response = $client->request('GET', '/api/my/index', [     'headers' => [         'Accept' => 'application/json',         'Authorization' => 'Bearer '.$accessToken,     ], ]); 

The API now should return

{"error":"unauthenticated"} 

whenever a token with under privilege is used to consumed restricted endpoint.

like image 114
Raymond Lagonda Avatar answered Sep 24 '22 19:09

Raymond Lagonda


Implement the Raymond Lagonda response and it works very well, just to be careful with the following. You need to override some methods from AuthenticatesUsers traits in ApiLoginController:

    /**      * Send the response after the user was authenticated.      *      * @param  \Illuminate\Http\Request  $request      * @return \Illuminate\Http\Response      */     protected function sendLoginResponse(Request $request)     {         // $request->session()->regenerate(); // coment this becose api routes with passport failed here.          $this->clearLoginAttempts($request);          return $this->authenticated($request, $this->guard()->user())                 ?: response()->json(["status"=>"error", "message"=>"Some error for failes authenticated method"]);      }      /**      * Get the failed login response instance.      *      * @param  \Illuminate\Http\Request  $request      * @return \Illuminate\Http\RedirectResponse      */     protected function sendFailedLoginResponse(Request $request)     {         return response()->json([                                 "status"=>"error",                                  "message"=>"Autentication Error",                                  "data"=>[                                     "errors"=>[                                         $this->username() => Lang::get('auth.failed'),                                     ]                                 ]                             ]);     } 

If you changed the login: username field to a custom username field eg: e_mail. You must refine the username method as in your LoginController. Also you have to redefine and edit the methods: validateLogin, attemptLogin, credentials since once the login is validated, the request is forwarded to passport and must be called username.

like image 42
Leonardo Jauregui Avatar answered Sep 25 '22 19:09

Leonardo Jauregui