基于用户角色为Laravel Passport添加Scopes的技术问询
Hey there! I get that building a secure SPA API from scratch can feel a bit overwhelming, especially when combining Passport and Laravel-permission for the first time. Let’s break this down into clear, actionable steps to tie user roles directly to Passport scopes.
1. Ensure Your Core Packages Are Set Up First
Before diving into scopes, make sure both Laravel Passport and Laravel-permission are properly installed and configured:
- Install Passport:
composer require laravel/passport php artisan migrate php artisan passport:install - Install Laravel-permission:
composer require spatie/laravel-permission php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" php artisan migrate
Don’t forget to add the HasApiTokens trait to your User model, plus the HasRoles trait from Laravel-permission to enable role management.
2. Define Your Passport Scopes
Scopes are the granular permissions your API will enforce (e.g., view-users, manage-posts). Register them in your AuthServiceProvider’s boot method:
use Laravel\Passport\Passport; public function boot() { $this->registerPolicies(); Passport::routes(); // Define your scopes with descriptive labels Passport::scope('view-users', 'View all user profiles') ->scope('manage-users', 'Create, edit, and delete users') ->scope('manage-posts', 'Create, edit, and delete blog posts'); }
If you haven’t already, run php artisan passport:client --personal to create a personal access client for your SPA.
3. Link Roles to Scopes (Many-to-Many Relationship)
Laravel-permission’s roles don’t come with a built-in link to Passport scopes, so we’ll create a pivot table to connect them:
- Generate the pivot table migration:
php artisan make:migration create_role_scope_table - Update the migration file to link roles to Passport’s
oauth_scopestable:public function up() { Schema::create('role_scope', function (Blueprint $table) { $table->foreignId('role_id')->constrained()->onDelete('cascade'); $table->foreignId('scope_id')->constrained('oauth_scopes')->onDelete('cascade'); $table->primary(['role_id', 'scope_id']); }); } - Run the migration:
php artisan migrate
4. Add Relationship to Your Role Model
Open your Role model (from Laravel-permission) and add a relationship to Passport’s Scope model:
use Laravel\Passport\Scope; class Role extends \Spatie\Permission\Models\Role { public function scopes() { return $this->belongsToMany(Scope::class, 'role_scope'); } }
5. Assign Scopes to Roles
Now you can assign scopes to roles—this can be done via seeders, a backend admin panel, or even Tinker for quick testing:
// Example: Assign full access scopes to an Admin role $adminRole = Role::findByName('admin'); $manageUsersScope = Scope::find('manage-users'); $managePostsScope = Scope::find('manage-posts'); $adminRole->scopes()->attach([$manageUsersScope->id, $managePostsScope->id]); // Example: Assign limited scopes to an Editor role $editorRole = Role::findByName('editor'); $viewUsersScope = Scope::find('view-users'); $managePostsScope = Scope::find('manage-posts'); $editorRole->scopes()->attach([$viewUsersScope->id, $managePostsScope->id]);
6. Attach Role Scopes When Issuing Access Tokens
When a user logs in and requests an access token, we need to automatically include all scopes associated with their roles. Update your login controller (or wherever you generate tokens) to do this:
public function login(Request $request) { $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required', ]); if (!Auth::attempt($credentials)) { return response()->json(['message' => 'Invalid credentials'], 401); } $user = Auth::user(); // Fetch all unique scopes from the user's assigned roles $scopes = $user->roles->flatMap(function ($role) { return $role->scopes->pluck('id')->toArray(); })->unique(); // Create token with the merged scopes $token = $user->createToken('SPA Access Token', $scopes)->accessToken; return response()->json(['access_token' => $token]); }
7. Protect Your API Routes with Scopes
Finally, use Passport’s scope middleware to protect your API endpoints. Only users with the required scope (via their role) will be able to access them:
use Illuminate\Support\Facades\Route; Route::middleware('auth:api')->group(function () { // Only users with 'view-users' scope can access this Route::get('/users', [UserController::class, 'index'])->middleware('scope:view-users'); // Only users with 'manage-users' scope can access this Route::post('/users', [UserController::class, 'store'])->middleware('scope:manage-users'); // Only users with 'manage-posts' scope can access these post routes Route::resource('/posts', PostController::class)->except(['create', 'edit'])->middleware('scope:manage-posts'); });
Bonus: Combine with Role Middleware (Optional)
For extra security, you can stack Laravel-permission’s role middleware alongside Passport’s scope middleware to enforce both role and scope checks:
Route::post('/users', [UserController::class, 'store']) ->middleware(['auth:api', 'role:admin', 'scope:manage-users']);
This setup ensures your API endpoints are only accessible to users with the right roles and corresponding scopes—no more unprotected routes!
内容的提问来源于stack exchange,提问作者Zero

