Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Passport Scopes Restriction Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with 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.
  • Requirements as of 3.0.1: a modern Laravel (11.x or 12.x — Laravel 12 support landed in this release) with the matching Passport major, and the PHP version Laravel 12 requires (8.2+). If you are still on Laravel 10, stay on the 2.x line.
  • Publish the config first: 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.
  • Second place to look: your AuthServiceProvider::boot() where Passport::tokensCan() declares the universe of scopes. The restriction layer only narrows that universe; it does not define scopes.
  • First use case: you ship a mobile/SPA client that must never be able to obtain 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.

Implementation Patterns

  • Single source of truth for scopes. Keep 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."
  • Per-client allowlists from the database. Store allowed scopes on the 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.
  • Per-user / role-derived restrictions. When the grant involves a user (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.
  • Register restrictions in a service provider, not the config file. Config is cached with php artisan config:cache, and closures cannot be serialized. Put the registration in boot() so config:cache in production does not explode.
  • Test the failure path, not just the happy path. Use 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.
  • Route middleware stays. This package governs what scopes are issued; ->middleware('scope:write') governs what scopes are accepted. Keep both, and assert both in tests.
  • Upgrade flow for 3.0.1. No application code changes are expected: the diff is CI action bumps plus Laravel 12 support. Bump the constraint, run your existing scope tests, and add Laravel 12 to your CI matrix in the same PR. If you rely on Laravel 12's new skeleton, verify the package's service provider is auto-discovered.
  • Environment parity. Keep real restrictions in 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.

Gotchas and Tips

  • Restriction ≠ authorization. A narrowed scope set does not protect routes that never declare scope: middleware. Audit routes with php artisan route:list -v and grep for missing scope middleware after adopting this package.
  • Client credentials has no user. Any callback that assumes $request->user() will fatal or return null for machine-to-machine grants. Branch on the grant type / null user explicitly.
  • Personal access tokens are the usual leak. Tokens created server-side via $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.
  • Know whether it filters or rejects. Decide up front whether an over-broad request should fail loudly (validation error, easier to debug, better for clients) or be silently trimmed (surprising, but sometimes desired). Verify the package's actual behavior with a quick request rather than assuming; this is the single most common misunderstanding of scope-restriction middleware.
  • Caching bites in two places. 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.
  • Debugging is easiest from inside the callback. Add a 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.
  • Extension points: the published config and, if exposed, the underlying contract for the restriction implementation. Bind your own implementation in a service provider rather than editing vendor files — this package is small enough that a custom binding is a realistic long-term strategy if your rules outgrow the callback.
  • Pin sensibly. 3.0.1 is a patch with no breaking changes; ^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.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log