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

Passport Multiauth Laravel Package

smartins/passport-multiauth

Extends Laravel Passport to support multiple authenticatable models and guards, such as users and admins, in one app. Map OAuth clients to guards, issue and validate tokens per provider, and keep authentication flows isolated without separate Passport installations.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin shim over Laravel Passport that lets you issue and validate tokens against multiple authenticatable models (e.g. User, Admin, Vendor) instead of the single App\Models\User Passport assumes out of the box. Internally it relies on Passport's provider column on oauth_clients and oauth_access_tokens to figure out which guard/model a token belongs to.

Minimal setup:

  1. Install (mind the version). The last tagged release is from April 2020, so constrain it against the Passport/Laravel versions you're actually running:

    composer require smartins/passport-multiauth
    

    If Composer blocks it, you'll likely need a fork or --with-all-dependencies plus a version pin. Treat this as the first thing to verify, before writing any code.

  2. Passport must already be installed and migrated (php artisan passport:install). The package composes with Passport — it does not replace it.

  3. Define one guard per actor type in config/auth.php. Each guard uses the passport driver but a distinct provider pointing at a distinct model:

    'guards' => [
        'api'        => ['driver' => 'passport', 'provider' => 'users'],
        'admin-api'  => ['driver' => 'passport', 'provider' => 'admins'],
    ],
    'providers' => [
        'users'  => ['driver' => 'eloquent', 'model' => App\Models\User::class],
        'admins' => ['driver' => 'eloquent', 'model' => App\Models\Admin::class],
    ],
    
  4. Create one Passport client per provider and copy the credentials into .env:

    php artisan passport:client --password --provider=admins --name="Admin Password Grant"
    php artisan passport:client --password --provider=users  --name="User Password Grant"
    
  5. Publish and wire up the package config (usually config/passport-multiauth.php). This is where you map each guard to its provider and client credentials:

    php artisan vendor:publish --provider="Smartins\PassportMultiauth\PassportMultiauthServiceProvider"
    
  6. Protect routes with the correct guard, e.g. Route::middleware('auth:admin-api').

Look here first when something is wrong: the published passport-multiauth.php (guard → client mapping) and config/auth.php (guard → provider → model). Ninety percent of bugs are a mismatch between those two files and the actual rows in oauth_clients.

First use case to prove it works: an admin SPA hits the token endpoint with the admin client's client_id/client_secret, gets a bearer token, and that token resolves to App\Models\Admin on an auth:admin-api route — no leakage into the api guard.

Implementation Patterns

One guard, one route group, one client. Keep the mapping 1:1 and boring. Segment routes/api.php explicitly:

Route::middleware('auth:api')->prefix('v1')->group(base_path('routes/api/user.php'));
Route::middleware('auth:admin-api')->prefix('v1/admin')->group(base_path('routes/api/admin.php'));

Never rely on the default guard inside shared code. auth()->user() returns whatever auth.defaults.guard is set to. In anything reused across actors, be explicit:

$admin = $request->user('admin-api');
$user  = $request->user('api');

Or call Auth::shouldUse('admin-api') at the top of a middleware/controller you know is admin-only.

Wrap token issuance in your own login controllers. Rather than hitting oauth/token from the frontend directly with client secrets, most teams add a LoginController per actor that calls the endpoint internally, picks the correct client ID/secret from config, and returns the token. This keeps secrets server-side and gives you a place to add rate limiting, MFA, etc. Use the package's access-token route or the standard Passport one — the key is passing the correct client credentials for that provider.

Refresh flow must stay within the same client. A refresh token issued to the admin client only refreshes against the admin client. Build separate /refresh endpoints per actor rather than trying to be clever.

Personal access tokens work per-model. As long as each model uses HasApiTokens, $admin->createToken('name', $abilities) works — but scope abilities per actor, they are not shared.

Authorisation. Register policies against each model explicitly (Gate::policy(Admin::class, AdminPolicy::class)). Don't try to unify User and Admin behind one policy — it always turns into a type check.

Testing. Use the guard name explicitly:

Passport::actingAs($admin, ['admin'], 'admin-api');

For unit tests without HTTP, actingAs($admin, 'admin-api') works as normal.

Frontend/SPA integration. Give each frontend its own client and its own /login endpoint. Do not share a Passport client across actor types — the client row is what carries provider, and swapping it out per request is where the whole approach falls apart.

Gotchas and Tips

Maintenance is the biggest gotcha. Last release April 2020. Expect Passport 10+/Laravel 9+ friction. Check the composer.json of the package against your Passport major before committing to it; if it doesn't install, plan to fork and adjust the illuminate/* constraints — usually only the service provider registration and one or two class imports need touching.

provider column is load-bearing. If oauth_clients.provider or oauth_access_tokens.provider is NULL or '' for a row, token resolution will fall back to the default model and you'll get Unauthenticated or, worse, the wrong user. Run passport:install fresh after configuring providers, and re-create clients any time you change a provider.

passport:install alone is not enough. It creates a default "Laravel Personal Access Client" and password client with no provider set. Those will authenticate against your default guard only. Create the per-provider clients explicitly with --provider=... and delete the defaults if you don't need them.

Env values in config get baked in. If you read env('ADMIN_CLIENT_ID') inside passport-multiauth.php and later run php artisan config:cache, the package will see a stale value if you rotate secrets. Either re-cache after rotation or reference config keys, not env directly, in the package config.

Debugging a 401:

  • SELECT id, user_id, client_id, provider, expires_at FROM oauth_access_tokens ORDER BY id DESC LIMIT 5; — check both provider and user_id are populated and match the right model's table.
  • SELECT id, provider, password_client FROM oauth_clients; — confirm the client row has the expected provider.
  • php artisan route:list --middleware=auth:admin-api — confirm the routes you think are protected are actually protected.
  • After any config change: php artisan config:clear && php artisan route:clear.

Guard misconfiguration looks like a Passport bug. If you see the token validating but $request->user('admin-api') returning null, the provider on the guard is almost always pointing at the wrong model or the wrong driver (eloquent vs database vs passport).

Route model binding and auth resolution are independent. A route like Route::get('/orders/{order}', ...) inside auth:admin-api will still bind Order — but if Order relationships or scopes assume a User, you'll get silent nulls. Be deliberate about which actor can see which resources; the package does not do any of that for you.

Extension points, in order of usefulness:

  • Override the package's access-token controller to add logging / tenancy checks before delegating to Passport.
  • Add a custom middleware that asserts the resolved user is an instance of the model expected for that guard — cheap safety net against provider misconfiguration.
  • Per-guard token lifetimes: register a small service provider that calls Passport::tokensExpireIn() differently per guard, wrapped in a middleware that flips values before each request. (Ugly but the only way without forking Passport.)

Don't try to make it do SSO across guards. "One token, all actors" is not what this package does. If a user and an admin both need access to the same resource, model that in your domain (a shared Actor concept, or role tables) rather than in the auth layer.

Fork-readiness matters. Given the release date, plan for the possibility that you will vendor the package. Keep your own integration confined to config/passport-multiauth.php and a single custom AccessTokenController so a future swap (or inlining the ~400 lines of package code) is a one-day job, not a rewrite.

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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
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