14

I am building REST API with JWT authentication and authorization with own logic. It's working perfectly. Now, I want to set the routes dynamically based on roles and permission. Suppose I have database structure like:

Role:

id  |   name
1   |  school
2   | transport

Permissions:

id  |   name                   |  controller         | routes
1   |  view-class-result       |  ApiController      | getClassResult
2   |  view-student-result     |  ApiController      | studentResult
3   |  download-student-result |  ApiController      | donwloadSchoolTemplate

Permission_role

role_id |  permission_id
1            1
1            2
1            3

Now, I want to create routes according to roles and permission in database.

Currently my routes seems like:

//All JWT authentication API goes here
Route::group(['middleware' => 'jwt.auth'], function() {
   Route::get('user', 'ApiController@getAuthUser');
   Route::get('invalidate', 'ApiController@invalidate');

   //All authorized API goes here
   Route::group(['middleware' => 'ability:school,view-class-result,true'], function() {
       Route::post('classResult', 'ApiController@getClassResult');
   });
   Route::group(['middleware' => 'ability:school,view-student-result,true'], function() {
       Route::post('studentResult', 'ApiController@studentResult');
   });
   Route::group(['middleware' => 'ability:school,download-student-result,true'], function() {
       Route::post('getStudentExamResult', 'ApiController@downloadSchoolTemplate');
   });
});

I don't want above routes to be hard coded. How can I get this routes from database. Something like below. But couldnot get idea how to do it.

In routes file,

$a = User:all();
foreach($a->roles as $value){
   foreach($value->permission as $val){

      Route::group(['middleware' => 'ability:{$value->name},{$val->name},true'], function() {
         Route::post('{$val->controller}', '{$val->controller}@{$val->method}');
      });

   }
}

Thank you.

Jai Chauhan
  • 4,035
  • 3
  • 36
  • 62
user254153
  • 1,855
  • 4
  • 41
  • 84
  • Your routes use the same middleware, why are u repeating them ? – Mahdi Younesi Feb 13 '18 at 03:19
  • I uses same middleware but different permissions. – user254153 Feb 13 '18 at 04:04
  • [Did you check this package](https://github.com/spatie/laravel-permission) ? – Niklesh Raut Feb 15 '18 at 10:03
  • 1
    Why do you not want to 'hardcode' the routes? A valid motivation has consequences for an appropriate answer. In your example, I don't see any reason why you wouldn't want to register these routes and would use middleware to handle your authorization in an ACL fashion. – Tom Feb 15 '18 at 11:07
  • The reason for not to hardcode routes is that. I want to control the authorization dynamically. Example: let's take role named, accountants who will be able to see entries, update statement, etc. If I hardcode the role name accountants, then I cannot changes the name of that role in future. If its dynamic then that could be change – user254153 Feb 15 '18 at 11:47
  • 1
    this is just a bad idea you don't want to pull your routes from database you can do it although but I won't recommended it – Bader Feb 15 '18 at 13:00
  • Use a role/permissions package with supplied middleware to do this. Just register all routes and handle authorization within middleware. Use permissions to allow/disallow actions and associate them with roles. You can rename/change roles/permissions afterward. https://github.com/spatie/laravel-permission#using-a-middleware – Tom Feb 15 '18 at 13:33
  • can you please share user table structure ? – Faraz Irfan Feb 16 '18 at 10:09
  • I also would not recommend doing this, it seems like it'd be hard to maintain in the future and i don't think this has been well thought out. Your routes file should read like the index page of a book imo. Also why would it matter if you change the name of your role? your permissions should contain some info about your route, the role just groups certain permissions together. – WhyAyala Feb 21 '18 at 00:39

4 Answers4

16

The best idea was using middleware parameter create Middleware call CheckPermission then you have to register that middleware into your app/Http/kernel.php file thats only you need check below code

Your kernel.php file

protected $routeMiddleware = [    
        'checkPermission' => \App\Http\Middleware\CheckPermission::class,
    ];

CheckPermission.php

    <?php

    namespace App\Http\Middleware;
    use Closure;
    use DB;

    class CheckPermission
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle($request, Closure $next,$permission_name)
        {
            //first check that name in your db
            $permission = DB::table('Permission')->where('name',$permission_name)->first()
            if($permission){
              //here you have to get logged in user role
              $role_id = Auth::user()->role;
              ## so now check permission
              $check_permission = DB::table('Permission_role')->where('role_id',$role_id)->where('permission_id',$permission->id)->first();
              if($check_permission){
                 return $next($request);
              }
              //if Permission not assigned for this user role show what you need
            }
            // if Permission name not in table then do what you need 
            ## Ex1 : return 'Permission not in Database';
            ## Ex2 : return redirect()->back();

        }
    }

Your Route file

 Route::group(['middleware' => 'jwt.auth'], function() {
        Route::post('classResult', 'ApiController@getClassResult')->middleware('checkPermission:view-class-result');
        Route::post('studentResult', 'ApiController@studentResult')->middleware('checkPermission:view-student-result');
        Route::post('getStudentExamResult', 'ApiController@downloadSchoolTemplate')->middleware('checkPermission:download-student-result');

   }
Hamelraj
  • 4,676
  • 4
  • 19
  • 42
  • I have done this already. I want to make that routes dynamic from database. Example: if I assign checkPermission role to view-class-result then It should be automatically reflect to routes. I don't want to hard code this. It is god idea to do so. – user254153 Feb 22 '18 at 09:33
  • above code you don't need to hard code. any way when you add new route you need to add name for that Ex `1 | view-class-result` then you have to assign to role, then have to assign in your route file – Hamelraj Feb 22 '18 at 11:04
  • or you don't want assign in your route file `Route::post('classResult', 'ApiController@getClassResult')->middleware('checkPermission:view-class-result');` like this – Hamelraj Feb 22 '18 at 11:05
  • you want Route::post('classResult', 'ApiController@getClassResult'); only this but persmission have to check from DB ?? any way you have to enter correct function name and controller name in your DB ?? – Hamelraj Feb 22 '18 at 11:06
  • nice. this is what i wanted. routes with dynamic role checking. thankyou. – axunic Oct 15 '19 at 05:33
0

So what you can do is make your role name accountants a value to key in the .env file and same for each and every role name.

In case you want to change it in near future you can change it manually in the .env file or you can make changes in the .env file via php code writtern on one of your Laravel function.

0

While I doubt that this is the best approach to this, but in your way of thinking you could try out this "pseudo code". I hope this expresses the basic idea. What that implies is:

  • A route pattern, not to include all routes explicitely in your routes file. ie. api/studentResult
  • Your controller to dispatch to the proper method that implements your api call via a single action controller (Link to documentation)
  • Your controller to load the correct middleware to take care on authorization

Routes

Route::group(['middleware' => 'jwt.auth'], function() {
    Route::get('user', 'ApiController@getAuthUser');
    Route::get('invalidate', 'ApiController@invalidate');

    // Choose whatever pattern you like...
    Route::post('api/{name}', ApiController::class);
});

Controller

class ApiController {

    public function __construct() {
        $permisions = $this->loadPersionForUser();

        $this->middleware('ability', [$permisions->value1, 'whatever']);
    }

    public function __invoke($method) {
        if (method_exists($this, $method)) {
            return $this->$method();
        }
    }
}

I'm not totally sure if you can load your middleware dynamically like this. If so, this could be a valid approach for this.

patriziotomato
  • 591
  • 1
  • 11
  • 25
0

In your routes.php

Route::group(['middleware' => 'jwt.auth'], function() {

Route::post('{uri}', 'AccessController@redirectURI');

});

Add this route at the end of all your routes.

Now create a new controller called AccessController and add the below constructor and method to it. Assuming user has a relationship with roles.

public function __construct(Request $request)
{
    $authorised_user = User::where('id', Auth::User()->id)->whereHas('role', function($query) use ($request)
            {
                $query->whereHas('permission', function($query) use ($request)
                {
                    $query->where('routes', $request->route('uri'))
                });
            })->firstOrFail();

            if( $authorised_user )
            {
                $permission = Permission::where('routes', $request->route('uri'))->findOrFail();

                $this->middleware([ 'ability:'.$authorised_user->role()->name.','.$permission->name.',true' ]);
            }
    else
    {
    // user is not authorised. Do what ever you want
    }

}

public function redirectURI($uri)
{
    $permission = Permission::where('routes', $uri)->findOrFail();

    return app('App\\Http\\Controllers\\'. $permission->controller )->$permission->method();
}

Overall it is retrieving the URL and comparing it against available routes in the authenticated user's permissions. If the authenticated user has the permission which the route belongs to, Then adding appropriate middleware.

Finally the redirectURI method is calling the appropriate controller method and returning the response.

Remember to replace the code with appropriate namespace and relations where ever necessary.