Today we will learn how to use the scopes and permission with API. In our example, we will create 3 different roles (admin, moderator, basic) with different permissions (view, add, edit, and delete). We will use the scope to moderate all the requests.
Table of content
- Define User Permission through Role
- Create Middleware to Attach Permission as Scope
- Define Scopes in Service Provider
- Generate Authentication Token with Scope
- Access Desired Area with Token
Define User Permission Through Role
The first step is defining the role of particular users. We are going to use 3 different permission i.e. admin, moderator and basic. These roles help to authenticate the user for desired actions
- admin – A user with admin permission can Add, Edit and View the user’s list.
- moderator – A user with moderator permission can Edit and View the user’s list.
- basic – A user with basic permission can only view the user’s list.
The first step is to create a migration for users and roles. We are going to use the make:model
command that will create the model and the migration for us. Let’s create the role migration and run the migration to add the roles table.
php artisan make:model -m Role
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->primary(['user_id', 'role']);
$table->integer('user_id')->unsigned();
$table->string('role');
});
}
php artisan migrate
Next, open the User Model and add the One-To-One relationship between User and Role Model.
public function role() {
return $this->hasOne(Role::class);
}
Now add data to the users and roles table. We are going to use the seed command to generate the data. Let’s create a factory to generate fake users and roles.
php artisan make:factory UserFactory
php artisan make:factory RoleFactory
Laravel uses the Faker package to generate fake data. Take a look at the UserFactory file.
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'phone' => $faker->tollFreePhoneNumber,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
});
This is our RoleFactory file.
$factory->define(Role::class, function (Faker $faker) {
return [
'role' => $faker->randomElement(['admin', 'moderator'])
];
});
Now create the UsersTableSeeder class through make:seed
command and use the Users and Role factory into it.
php artisan make:seed UsersTableSeeder
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
factory(App\User::class, 3)->create()
->each(function($user) {
$user->role()->save(factory(App\Role::class)->make());
});
}
}
Now attach the UsersTableSeeder in DatabaseSeeder class and run the db:seed
command to add the data to the database.
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call(UsersTableSeeder::class);
}
}
php artisan db:seed
Create Middleware to Attach Permission as Scope
The next step is to create a middleware with make:middleware
command. We are going to add scope as extra parameter to every request. The scope parameter should contain the role of the authenticated user.
php artisan make:middleware CheckRole
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$userRole = $request->user()->role()->first();
if ($userRole) {
// Set scope as admin/moderator based on user role
$request->request->add([
'scope' => $userRole->role
]);
}
return $next($request);
}
}
Now attach our ChecRole middleware to karnel.php
file.
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'role' => \App\Http\Middleware\CheckRole::class,
];
Now open the api.php
file and attach the role middleware to the authenticated routes.
Route::post('login', 'AuthController@login');
Route::middleware(['auth:api', 'role'])->group(function() {
....
....
....
});
Define Scopes in Service Provider
Now time to define the Scopes in the AuthServiceProvider class. Make sure you have installed the Laravel Passport. I have created a separate post to generate the access token with Laravel Passport that you can check for the detailed description.
Open the AuthServiceProvider.php
file from /app/Providers directory and define the scopes and the default scope.
public function boot()
{
$this->registerPolicies();
Passport::routes();
// Mandatory to define Scope
Passport::tokensCan([
'admin' => 'Add/Edit/Delete Users',
'moderator' => 'Add/Edit Users',
'basic' => 'List Users'
]);
Passport::setDefaultScope([
'basic'
]);
}
Next, we have add the scopes middleware to the kernal.php
file. Now open the kernal.php
file and add the scopes middleware.
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
'role' => \App\Http\Middleware\CheckRole::class,
];
Generate Authentication Token with Scope
Now its time to install the Laravel Passport. I am direct dive to the login method part to generate the access token with scope.
Now open the AuthController.php
file and generate the access token with scope.
public function login(Request $request)
{
$request->validate([
'email' => 'required|email|exists:users,email',
'password' => 'required'
]);
if( Auth::attempt(['email'=>$request->email, 'password'=>$request->password]) ) {
$user = Auth::user();
$userRole = $user->role()->first();
if ($userRole) {
$this->scope = $userRole->role;
}
$token = $user->createToken($user->email.'-'.now(), [$this->scope]);
return response()->json([
'token' => $token->accessToken
]);
}
}
Access Desired Area with Token
Now its time to add the routes with specific permissions and test if everything working fine. Let’s add the routes to the api.php
file.
Route::middleware(['auth:api', 'role'])->group(function() {
// List users
Route::middleware(['scope:admin,moderator,basic'])->get('/users', function (Request $request) {
return User::get();
});
// Add/Edit User
Route::middleware(['scope:admin,moderator'])->post('/user', function(Request $request) {
return User::create($request->all());
});
Route::middleware(['scope:admin,moderator'])->put('/user/{userId}', function(Request $request, $userId) {
try {
$user = User::findOrFail($userId);
} catch (ModelNotFoundException $e) {
return response()->json([
'message' => 'User not found.'
], 403);
}
$user->update($request->all());
return response()->json(['message'=>'User updated successfully.']);
});
// Delete User
Route::middleware(['scope:admin'])->delete('/user/{userId}', function(Request $request, $userId) {
try {
$user = User::findOfFail($userId);
} catch (ModelNotFoundException $e) {
return response()->json([
'message' => 'User not found.'
], 403);
}
$user->delete();
return response()->json(['message'=>'User deleted successfully.']);
});
});
We have created the following routes in api.php
file.
- GET /users – List all the users
- POST /user – Add new user
- PUT /user/{userId} – Update existing user
- DELETE /user/{userId} – Delete existing user
All these routes work based on roles that we already defined in the role section. Let’s take a look at the database for the users and roles table.
Let’s create the access token for the user with the basic role. I am using the PostMan to test the created API.
Now let’s use this access token to update the user information with PUT /user/{userId}
endpoint.
We are going to update the phone number of the user. Do not forget to attach the access token to the request header.
Now request for updating the phone number.
The user does not have permission to update the user data. And that is because of the basic permission. Only the moderator and admin can update the user data. And we have defined that in our api.php
route by applying middleware.
// admin and moderator can access this area
Route::middleware(['scope:admin,moderator']);
// Add/Edit User
Route::middleware(['scope:admin,moderator'])->post('/user', function(Request $request) {
return User::create($request->all());
});
Route::middleware(['scope:admin,moderator'])->put('/user/{userId}', function(Request $request, $userId) {
try {
$user = User::findOrFail($userId);
} catch (ModelNotFoundException $e) {
return response()->json([
'message' => 'User not found.'
], 403);
}
$user->update($request->all());
return response()->json(['message'=>'User updated successfully.']);
});
Now let’s create another access token with moderator user and request to update the phone number.
Please feel free to contact me for the suggestion or stuck with any issue by following this post.