Skip to content
Mehedi Hasan Sagor

// blog

Laravel API Authentication: Sanctum vs Passport – Choosing the Right Tool for Your Product

Mehedi Hasan Sagor Oct 24, 2026 5 min read
Laravel API Authentication: Sanctum vs Passport – Choosing the Right Tool for Your Product

Why API authentication matters

When a startup launches its first public API, the choice of authentication mechanism can set the tone for security, developer experience, and future scalability. Laravel ships with two first‑party solutions: Sanctum and Passport. Both are battle‑tested, but they solve different problems. This post walks through their core concepts, shows minimal implementation code, and provides a decision matrix so you can pick the right tool without re‑architecting later.

Sanctum – Simple token & SPA authentication

Sanctum is designed for two main use‑cases:

  1. Single‑page applications (SPA) that share the same first‑party domain as the Laravel backend.
  2. Mobile or third‑party clients that need lightweight, token‑based authentication.

How it works

Sanctum issues personal access tokens (PATs) stored in the personal_access_tokens table. The token is a plain‑text string hashed with SHA‑256, similar to how Laravel stores passwords. For SPAs, Sanctum also provides cookie‑based session authentication using Laravel’s built‑in CSRF protection.

Minimal setup

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Add the middleware to api group in app/Http/Kernel.php:

'api' => [
    \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

Create a token for a user:

$token = $user->createToken('mobile-app')->plainTextToken;
return response()->json(['token' => $token]);

Protect routes with the auth:sanctum guard:

Route::middleware('auth:sanctum')->get('/profile', function (Request $request) {
    return $request->user();
});

When Sanctum shines

  • Fast onboarding – No OAuth server, no extra database tables beyond the token table.
  • Stateless mobile APIs – Tokens are sent in the Authorization: Bearer header, no cookie handling needed.
  • Same‑origin SPAs – The cookie flow avoids the overhead of OAuth redirects.
  • Low traffic – Suitable for early‑stage products where request volume is modest.

Passport – Full OAuth2 server

Passport implements the OAuth2 Authorization Server spec. It is ideal when you need:

  1. Third‑party developers to obtain access on behalf of users (e.g., “Sign in with Google” style).
  2. Fine‑grained scopes to limit what a token can do.
  3. Refresh tokens for long‑lived sessions without re‑authenticating.

How it works

Passport spins up its own set of tables (oauth_clients, oauth_access_tokens, etc.) and provides a token endpoint that issues JWT‑like access tokens signed with Laravel’s encryption key. It also supports Personal Access Tokens similar to Sanctum, but under the OAuth umbrella.

Minimal setup

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

In AuthServiceProvider register the routes:

public function boot()
{
    $this->registerPolicies();
    Passport::routes();
}

Add the api guard in config/auth.php:

'guards' => [
    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],

Issue a token using the password grant (example controller):

public function login(Request $request)
{
    $http = new \GuzzleHttp\Client;
    $response = $http->post(config('services.passport.login_endpoint'), [
        'form_params' => [
            'grant_type' => 'password',
            'client_id' => config('services.passport.client_id'),
            'client_secret' => config('services.passport.client_secret'),
            'username' => $request->email,
            'password' => $request->password,
            'scope' => '',
        ],
    ]);
    return json_decode((string) $response->getBody(), true);
}

Protect routes with the auth:api middleware (which now points to Passport):

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

When Passport shines

  • Third‑party integrations – You can expose an OAuth2 authorization endpoint for external apps.
  • Scope‑based access control – Fine‑tune permissions per token.
  • Refresh token flow – Seamless long‑lived sessions without re‑prompting credentials.
  • Enterprise‑grade security – Supports PKCE, client credentials, and more.

Decision matrix

| Feature | Sanctum | Passport | |---------|---------|----------| | OAuth2 spec compliance | No | Full | | Personal access tokens | ✅ | ✅ (via passport:client) | Refresh tokens | ❌ (you must implement) | ✅ | | Scopes | ❌ (you can add manually) | ✅ | | First‑party SPA (same domain) | ✅ (cookie flow) | ❌ (requires redirect) | | Third‑party developer ecosystem | ❌ | ✅ | | Setup complexity | Low (one table) | Higher (multiple tables, client registration) | | Performance overhead | Minimal (simple DB lookup) | Slightly higher (token introspection) | | Learning curve | Easy | Moderate to high |

Quick rule of thumb

  • If your API is only consumed by your own web app or a native mobile app you control, start with Sanctum. It keeps the codebase lean and avoids the OAuth ceremony.
  • If you plan to open your API to external partners, need granular scopes, or want refresh‑token support out of the box, go with Passport.

Migrating from Sanctum to Passport (or vice‑versa)

Changing the authentication layer later can be painless if you keep a thin abstraction in your controllers:

use Illuminate\Support\Facades\Auth;

public function me(Request $request)
{
    $user = Auth::guard('api')->user(); // guard resolves to sanctum or passport
    return response()->json($user);
}

When you switch guards in config/auth.php, the same route works. The only migration work is:

  1. Create the new client records (passport:install).
  2. Issue new tokens for existing users – you can run a one‑off script that calls $user->createToken(...) for Sanctum or creates an OAuth access token via Passport’s PasswordGrantClient.
  3. Update token storage – revoke old tokens to avoid confusion.

Takeaways you can act on today

  1. Audit your product roadmap – If you foresee external developers using your API, allocate time for Passport now; retrofitting later adds friction.
  2. Start small – Use Sanctum for MVP APIs. It costs a single migration step if you later need OAuth.
  3. Separate concerns – Keep authentication logic in a service class that reads the guard name from config. This makes swapping implementations a one‑line change.
  4. Monitor token usage – Regardless of the tool, log token creation and revocation events. Laravel’s built‑in events (PersonalAccessTokenCreated, AccessTokenCreated) can feed a simple audit table.
  5. Secure the token transport – Always enforce HTTPS, set SameSite=Lax for cookies, and consider short‑lived access tokens with refresh tokens (Passport) or rotate PATs regularly (Sanctum).

By aligning the authentication strategy with your product’s growth stage, you avoid costly rewrites and keep your API both developer‑friendly and secure.

  • Laravel
  • Api
  • Security
  • Authentication