denniseilander/laravel-passport-scopes-restriction
Restrict which OAuth scopes users can request with Laravel Passport. Assign allowed scopes per user or model and enforce them during token issuance, so clients can't obtain permissions they shouldn't have—keeping API access tightly scoped with minimal setup.
composer require denniseilander/laravel-passport-scopes-restriction. This is a thin add-on, not a Passport replacement — Passport must already be installed, migrated, and issuing tokens before this package does anything useful.php artisan vendor:publish --tag="passport-scopes-restriction-config". Read the published config/passport-scopes-restriction.php before writing any code — it is the contract you will be configuring against.AuthServiceProvider::boot() where Passport::tokensCan() declares the universe of scopes. The restriction layer only narrows that universe; it does not define scopes.write:* scopes even if a malicious caller asks for them. Register a restriction callback that returns the allowed subset for that client, then assert with a feature test that requesting write yields an error instead of a token.Passport::tokensCan() as the master list and treat the restriction callback purely as a filter over it. Never hardcode scope strings in controllers, middleware, and the callback independently — one typo silently becomes "scope never grantable."oauth_clients table (a JSON column or a pivot) or in your own client_scope_restrictions table, and have the callback look up by client ID. Cache the map (Cache::remember("client-scopes:{$id}", ...)) and bust it when a client is edited.authorization_code, password), derive the allowed set from roles or plan tier. Keep the mapping in a dedicated class or config array rather than an inline closure so it is unit-testable.php artisan config:cache, and closures cannot be serialized. Put the registration in boot() so config:cache in production does not explode.Passport::actingAs($user, ['read']) for convenience, and for the real check hit POST /oauth/token with a client that requests a forbidden scope; assert the response is a denial and that no row was written to oauth_access_tokens.->middleware('scope:write') governs what scopes are accepted. Keep both, and assert both in tests.config/ per environment but avoid a "wide open in local" default that hides bugs — an app()->isLocal() escape hatch is the classic way a scope bug reaches production.scope: middleware. Audit routes with php artisan route:list -v and grep for missing scope middleware after adopting this package.$request->user() will fatal or return null for machine-to-machine grants. Branch on the grant type / null user explicitly.$user->createToken('name', $scopes) do not travel through the OAuth token endpoint, so an HTTP-layer restriction will not touch them. Enforce allowed scopes at your own token-creation service too.config:cache (closures in config) and Passport's own client/token caching when you change a client's allowed scopes — remember to invalidate your own scope cache or restart workers under Octane.Log::debug('scope restriction', ['client' => $clientId, 'requested' => $scopes, 'allowed' => $allowed]) line temporarily; you will otherwise be guessing whether the restriction layer, Passport's own validation, or route middleware rejected the request.^3.0 is safe. Watch the README on the 3.x branch rather than blog posts — the API surface is small and version-specific examples age badly.How can I help you explore Laravel packages today?