CCelestiallines.com
← Back to blog

Laravel Authentication: Breeze vs Sanctum vs Passport

By Pawan Singh · Jun 2026

These three solve different problems and get reached for interchangeably far too often. Picking based on "which tutorial I found" instead of "which problem I actually have" is the most common authentication mistake in Laravel projects.

Breeze: Session-Based Auth for a Server-Rendered or Same-Origin App

Breeze scaffolds traditional cookie-session authentication — login, registration, password reset, email verification — as real, editable code in your own project rather than a hidden package:

composer require laravel/breeze --dev
php artisan breeze:install
php artisan migrate

Use Breeze when your frontend is Blade views, or an Inertia-powered React/Vue app served from the same Laravel app (not a separately hosted frontend). The user stays logged in via a standard session cookie, exactly like a classic web app. It is not an API authentication solution by itself — it's for browser sessions.

Sanctum: Tokens for SPAs and Mobile Apps You Control

Sanctum solves a different problem: a separate frontend (a mobile app, or an SPA hosted on its own domain) needs to authenticate against your Laravel API. It supports two distinct modes, and mixing them up is where most Sanctum confusion comes from:

  • API tokens — for mobile apps or third-party-ish clients. Issue a token after login, the client sends it as Authorization: Bearer {token} on every request:
    $token = $user->createToken('mobile-app')->plainTextToken;
  • SPA cookie authentication — for a first-party JavaScript frontend on a different subdomain of the same site. No visible token at all; Sanctum instead uses Laravel's normal session cookie plus CSRF protection, treating the SPA as "stateful" if its domain is listed in SANCTUM_STATEFUL_DOMAINS. The frontend must call /sanctum/csrf-cookie before its first request.

Protect routes the same way regardless of which mode is in use:

Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('posts', PostController::class);
});

Revoke a specific token with $user->currentAccessToken()->delete(), or every token for a user (e.g. on a "log out everywhere" action) with $user->tokens()->delete(). Tokens can also carry abilities (scopes) — createToken('mobile-app', ['posts:read']) — letting you issue a token that can only perform specific actions, checked with $request->user()->tokenCan('posts:read').

Passport: Full OAuth2 for Third Parties You Don't Control

Passport implements the complete OAuth2 spec — authorization codes, scopes, refresh tokens, client credentials. It solves a genuinely different problem than Sanctum: letting a third-party application you don't control request scoped, revocable, user-consented access to your API on a user's behalf. This is the "Log in with X" / "Connect your account" pattern, from the provider side.

composer require laravel/passport
php artisan migrate
php artisan passport:install

If nobody outside your own apps needs to request access to a user's data, you almost certainly don't need Passport — it's noticeably more setup and conceptual overhead than Sanctum for a problem most projects don't actually have. A very common mistake is choosing Passport by default for "a real API" when Sanctum (or even Breeze) already covers the actual requirement.

Which One Do You Actually Need?

  • Traditional server-rendered app or an Inertia SPA on the same domain — Breeze.
  • Your own mobile app, or your own SPA on a different subdomain, calling your API — Sanctum.
  • A third party you don't control needs delegated, scoped, revocable access to a user's data — Passport, and only then.

These aren't mutually exclusive within one project, either — it's entirely normal for an app to use Breeze for the browser-facing dashboard and Sanctum for a companion mobile app hitting the same backend.

Common Issues

  • Sanctum "CSRF token mismatch" from an SPA — forgot to call /sanctum/csrf-cookie before the first stateful request, or the frontend domain isn't listed in SANCTUM_STATEFUL_DOMAINS.
  • Sanctum bearer token works in Postman but the SPA still isn't authenticated — the SPA is likely using cookie-based stateful auth, not bearer tokens; the two modes aren't interchangeable for the same request.
  • Passport client credentials expire unexpectedly — refresh token handling wasn't implemented on the client side; OAuth2 access tokens are intentionally short-lived and expected to be refreshed.
  • Chose Passport, project complexity ballooned for no real benefit — a strong signal the actual requirement was Sanctum all along.

Not sure which authentication approach fits your project? Get in touch — I work as a freelance Laravel developer.

Comments