Reputation: 1883
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.
Upvotes: 14
Views: 7275
Reputation:
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.
Upvotes: 0
Reputation: 4826
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');
}
Upvotes: 16
Reputation: 639
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:
api/studentResult
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);
});
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.
Upvotes: 0
Reputation: 56
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.
Upvotes: 0