Passport is Laravel's full OAuth2 server implementation, built on the league/oauth2-server package. It's the right tool when you're actually building an OAuth2 provider — third-party developers requesting access on behalf of users, or a first-party SPA and mobile app that both need token-based auth against the same API. If you just need "an API token for my own frontend," Sanctum is almost always the better fit; Passport's complexity earns its keep only when you need real OAuth2 semantics: scopes, multiple grant types, and clients you don't control.
Which grant type you actually need
Passport supports authorization code (with PKCE), client credentials, and personal access tokens as its main grant types today. The password grant and the implicit grant are both deprecated in OAuth2.1 and Passport has followed suit — password grant because it teaches users to type credentials into a third-party app, which defeats the point of OAuth, and implicit because returning tokens in a URL fragment is a security liability now that authorization code with PKCE works fine for public clients (SPAs, mobile apps) without needing a client secret. If you're starting a new Passport integration in 2026, authorization code + PKCE for user-facing clients and client credentials for machine-to-machine is the whole decision tree.
// config/auth.php — api guard using passport driver
'guards' => [
'api' => ['driver' => 'passport', 'provider' => 'users'],
],
// Machine-to-machine client: no user, just service identity
Route::post('/oauth/token', [AccessTokenController::class, 'issueToken'])
->middleware('throttle');
// Requesting a token as a service client
// POST /oauth/token
// grant_type=client_credentials
// client_id=...&client_secret=...&scope=reports:read
Route::middleware(['auth:api'])->group(function () {
Route::get('/reports', function (Request $request) {
abort_unless($request->user()->tokenCan('reports:read'), 403);
return Report::latest()->paginate();
});
});
Scopes are the part teams skip
It's easy to wire up Passport, issue tokens, and never define a single scope — every token ends up with implicit access to everything the guard allows. That works until you need to hand a token to a partner integration that should only read invoices, not write them. Define scopes early (`Passport::tokensCan([...])`) even if you only have one for now; retrofitting scopes onto tokens already in the wild means you can't tighten access without breaking existing integrations.
Passport's default token lifetimes are long. Set explicit expiry with Passport::tokensExpireIn() and Passport::refreshTokensExpireIn() in a service provider's boot() method. Short-lived access tokens plus refresh tokens is the standard shape — it limits the blast radius of a leaked access token without forcing users to re-authenticate constantly.
Revocation and refresh token rotation
Passport supports refresh token rotation — each time a refresh token is used, it's revoked and a new one issued. This matters for detecting token theft: if a stolen refresh token gets used after the legitimate client has already rotated past it, Passport's pruning and revocation logic can catch the reuse. Run passport:purge on a schedule to clear expired and revoked tokens from the database; without it, the oauth_access_tokens table grows unbounded and every auth check pays a small tax querying rows nobody needs anymore.
Deactivating a user account doesn't automatically invalidate tokens already issued to them. Hook into the deactivation flow to call $user->tokens()->each(fn($t) => $t->revoke()) — otherwise a suspended user's existing session keeps working until the token naturally expires.
Passport vs. Sanctum, the decision that actually matters
The most common Passport mistake is choosing it for a same-origin SPA that only ever talks to its own backend. Sanctum's cookie-based SPA authentication solves that case with a fraction of the moving parts — no OAuth clients table, no scopes, no grant type decision tree. Passport earns its complexity when there's a real third party in the picture: a mobile app shipped to app stores that can't hold a secret safely, a partner's server calling your API under its own client credentials, or an actual "login with YourApp" flow for other developers' products.
Wrapping up
Passport is correct when you need real OAuth2 — multiple untrusted clients, scoped access, a token authority that isn't just "my own frontend." Use authorization code with PKCE for anything with a user in the loop, client credentials for service-to-service, define scopes from day one, and set explicit token TTLs. If none of that applies to your app, you probably want Sanctum instead.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.