Laravel Passport – Working with Authentication, Scope, and Permission

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

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.

users table
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.

Laravel Generate Access Token

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.

Attach access token to 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.

Moderator Access Token
Update response for moderator

Please feel free to contact me for the suggestion or stuck with any issue by following this post.