DEV Community

Cover image for Laravel Passport vs Sanctum: Which One Do You Actually Need?
Hafiz
Hafiz

Posted on • Originally published at hafiz.dev

Laravel Passport vs Sanctum: Which One Do You Actually Need?

Originally published at hafiz.dev


The Short Answer

For most Laravel projects, Sanctum is the right choice. Use it for your SPA, mobile app, or simple API token authentication. Passport is only necessary when third-party developers need OAuth2 access to your API.

I've shipped production apps with both. When building StudyLab (an AI quiz generator used by 500+ schools), I went with Sanctum. The API serves our web app and potential mobile clients. All first-party. No external developers asking for API access. Sanctum took about 5 minutes to set up and hasn't given me a single headache since.

Here's the decision in one sentence: If you're not building a public API platform for third-party developers, use Sanctum.

Now let me show you exactly why.


Quick Comparison: Sanctum vs Passport

Feature Sanctum Passport
Primary Use Case First-party SPAs, mobile apps Third-party API access, OAuth2
OAuth2 Support No Full implementation
Setup Complexity Simple (5 mins) Moderate (15-30 mins)
Token Types Personal access tokens, session cookies Access tokens, refresh tokens, all OAuth2 grants
Best For Internal apps, simple APIs Enterprise, third-party integrations
Ships with Laravel Yes (default) Separate package
Learning Curve Low Moderate to High
Database Tables 1 (personal_access_tokens) 5+ (clients, tokens, refresh tokens, etc.)
Token Size ~40 characters ~1000+ characters (JWT)

That table tells most of the story. But let's dig into the details.


What is Laravel Sanctum?

Sanctum is Laravel's lightweight authentication package for SPAs, mobile applications, and simple token-based APIs. It ships with Laravel by default since version 8.x, which tells you something about what the Laravel team recommends for most projects.

Sanctum handles two distinct authentication scenarios:

1. Session-Based Authentication (SPAs)

When your frontend lives on the same domain as your Laravel backend, Sanctum uses Laravel's built-in session authentication. Your SPA makes a request to /sanctum/csrf-cookie, grabs the CSRF token, and then authenticates through your standard login endpoint. No tokens to manage. No localStorage security concerns. Just cookies.

2. Token-Based Authentication (Mobile Apps & APIs)

For mobile apps or when your client can't use cookies, Sanctum issues personal access tokens. These are simple, opaque strings stored in your database. You send them in the Authorization header, and Sanctum validates them against the personal_access_tokens table.

Here's how simple the setup is:


php artisan install:api

// Creating a token with abilities (scopes)
$token = $user->createToken('mobile-app', ['posts:read', 'posts:write']);
return $token->plainTextToken;

// Protecting routes
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn(Request $request) => $request->user());
    Route::get('/posts', [PostController::class, 'index']);
});

// Checking abilities in your controller
if ($request->user()->tokenCan('posts:write')) {
    // User can create posts
}

// Revoking tokens
$user->tokens()->delete(); // All tokens
$user->currentAccessToken()->delete(); // Current token only
Enter fullscreen mode Exit fullscreen mode

That's it. No OAuth clients to create. No encryption keys to generate. No grant types to understand.

For ReplyGenius, my Chrome extension that authenticates with a Laravel backend, this token-based approach was perfect. The extension stores the token, sends it with each request, and I never had to think about OAuth2 flows or refresh token rotation.


What is Laravel Passport?

Passport is Laravel's full OAuth2 server implementation. It's built on top of the League OAuth2 Server package and provides everything you need to run a complete OAuth2 authorization server.

This matters when you're building something like Twitter's API or Stripe's API. External developers create applications, your users authorize those applications, and tokens get issued through proper OAuth2 flows.

Passport supports all the standard OAuth2 grant types:

Authorization Code Grant - The standard OAuth2 flow. Third-party apps redirect users to your site, users approve access, your site redirects back with a code, and the app exchanges that code for tokens. This is what you see when you click "Login with GitHub" or "Connect to Slack."

Client Credentials Grant - Machine-to-machine authentication. No user involved. One server talks to another. Perfect for microservices or backend integrations.

Password Grant - User provides username and password directly to the client app. Generally not recommended anymore since it requires the third-party app to handle user credentials.

Refresh Tokens - Access tokens expire. Refresh tokens let clients get new access tokens without requiring the user to re-authenticate.

Here's the Passport setup:

// Installation
php artisan install:api --passport

// In your User model
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
use Carbon\CarbonInterval;

class User extends Authenticatable implements OAuthenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}

// Define token scopes in AuthServiceProvider
public function boot(): void
{
    Passport::tokensCan([
        'read-posts' => 'Read your posts',
        'write-posts' => 'Create and update posts',
        'delete-posts' => 'Delete your posts',
    ]);

    // Optional: Set token expiration
    Passport::tokensExpireIn(CarbonInterval::days(15));
    Passport::refreshTokensExpireIn(CarbonInterval::days(30));
}

// Creating a personal access token (similar to Sanctum)
$token = $user->createToken('My Token', ['read-posts'])->accessToken;

// Protecting routes
Route::middleware('auth:api')->group(function () {
    Route::get('/user', fn(Request $request) => $request->user());
});

Enter fullscreen mode Exit fullscreen mode

Notice the difference? More moving parts. More configuration. More database tables. Passport creates OAuth clients, manages token encryption keys, and handles the full OAuth2 dance.


When to Use Sanctum

Use Sanctum when you control all the clients consuming your API. That's the key insight.

Your SPA lives with your Laravel app. Same team, same codebase (or at least same organization). You're not exposing an API for random developers to build against. You're building your own product.

Your mobile app is first-party. You're building the iOS or Android app that talks to your Laravel backend. The app isn't a third-party integration; it's part of your product.

You need simple API tokens. Maybe you're building a service where users generate API keys to access their own data. Think personal access tokens for automation or integrations the user controls.

You want minimal complexity. Sanctum requires understanding two concepts: session cookies and bearer tokens. Passport requires understanding OAuth2, which is a specification with its own RFC documents.

When I built StudyLab's API, the decision was obvious. Schools access the platform through our web app. Teachers use our interface. Students use our interface. If we build a mobile app later, we build it. No school is asking to integrate StudyLab into their custom LMS through an API we provide.

Sanctum. Done.

The setup took about 5 minutes. I've spent zero hours debugging OAuth2 flows or explaining to myself why a token refresh failed.


When to Use Passport

Passport earns its complexity in specific scenarios. Here's when you actually need it:

Third-party developers will build against your API. You're creating a platform. Other companies or developers will create applications that access your users' data with permission. Think Shopify apps, Slack integrations, or GitHub OAuth apps.

You need OAuth2 compliance. Some enterprise clients require OAuth2. It's in the procurement checklist. Your authentication needs to follow the spec, issue proper JWT tokens, and support standard OAuth2 endpoints.

You're building a public API platform. You want developers to register applications, get client credentials, and go through proper authorization flows. You need rate limiting per client, application-specific scopes, and the ability to revoke access for specific third-party apps.

Machine-to-machine authentication. Your Laravel app needs to authenticate requests from other backend services where no user is involved. The Client Credentials grant handles this cleanly.

Here's a concrete example: If StudyLab offered a public API for schools to build custom dashboards or integrate quiz data into their existing systems, I'd need Passport. Those integrations are third-party apps. They'd need to request access on behalf of school administrators. OAuth2 flows would handle the authorization properly.

But StudyLab doesn't offer that. So I don't use Passport.


Can You Use Both Together?

Yes. But you probably don't need to.

The scenario where this makes sense: you have a first-party SPA (Sanctum) AND you're offering a public API for third-party developers (Passport). Different clients, different authentication mechanisms, different route groups.

// routes/api.php

// First-party SPA routes - Sanctum
Route::middleware('auth:sanctum')->prefix('v1')->group(function () {
    Route::get('/user', [UserController::class, 'show']);
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

// Third-party API routes - Passport
Route::middleware('auth:api')->prefix('public/v1')->group(function () {
    Route::get('/user', [PublicApiUserController::class, 'show']);
    Route::get('/posts', [PublicApiPostController::class, 'index']);
});
Enter fullscreen mode Exit fullscreen mode

I've never needed this setup in practice. If you're considering it, ask yourself: do I really have two distinct types of API consumers, or am I overengineering?


Migrating from Passport to Sanctum

Here's something I see often: developers who chose Passport early in a project and later realized they didn't need OAuth2. The migration is straightforward if you're only using personal access tokens.

Step 1: Sanctum is already installed

Since Laravel 8, Sanctum ships by default. Check your config/sanctum.php exists. If not:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Enter fullscreen mode Exit fullscreen mode

Step 2: Update your User model

// Before (Passport)
use Laravel\Passport\HasApiTokens;

// After (Sanctum)
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
}
Enter fullscreen mode Exit fullscreen mode

Same trait name, different namespace. Convenient.

Step 3: Update your auth guard

In config/auth.php:

'guards' => [
    'api' => [
        'driver' => 'sanctum', // Changed from 'passport'
        'provider' => 'users',
    ],
],
Enter fullscreen mode Exit fullscreen mode

Step 4: Update route middleware

// Before
Route::middleware('auth:api')->group(...);

// After (you can use either)
Route::middleware('auth:sanctum')->group(...);
// Or keep 'auth:api' if you updated the guard config
Enter fullscreen mode Exit fullscreen mode

Step 5: Update token creation

// Passport
$token = $user->createToken('name', ['scope'])->accessToken;

// Sanctum  
$token = $user->createToken('name', ['ability'])->plainTextToken;
Enter fullscreen mode Exit fullscreen mode

Note the difference: Passport returns ->accessToken, Sanctum returns ->plainTextToken.

Step 6: Clean up Passport

Remove the package and its database tables:

composer remove laravel/passport
php artisan migrate:rollback --path=vendor/laravel/passport/database/migrations
Enter fullscreen mode Exit fullscreen mode

Test thoroughly. Your existing tokens won't work anymore since they're stored in different tables with different formats. Plan for users to re-authenticate.


Common Mistakes to Avoid

1. Using Passport when Sanctum suffices

This is the big one. OAuth2 sounds impressive. It's also unnecessary complexity for most applications. If you're not building a public API platform, you're overengineering.

2. Forgetting CSRF protection with Sanctum SPAs

When using Sanctum's session-based auth for SPAs, you need the EnsureFrontendRequestsAreStateful middleware. Without it, your SPA requests won't include session cookies properly.

// Laravel 11+ (bootstrap/app.php)
->withMiddleware(function (Middleware $middleware) {
    $middleware->statefulApi();
})

// Laravel 10 and earlier
// Add to 'api' middleware group:
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
Enter fullscreen mode Exit fullscreen mode

3. Not setting token abilities

Always scope your tokens. Even for internal apps.

// Bad
$token = $user->createToken('api')->plainTextToken;

// Good
$token = $user->createToken('api', ['posts:read', 'posts:write'])->plainTextToken;
Enter fullscreen mode Exit fullscreen mode

If a token gets compromised, limited abilities reduce the damage.

4. Storing tokens insecurely

For SPAs on the same domain, use Sanctum's session-based auth. No tokens in localStorage. No XSS risk for your auth credentials.

For mobile apps, use secure storage. iOS Keychain. Android Keystore. Not SharedPreferences in plain text.

5. Not revoking tokens on logout

// In your logout controller
public function logout(Request $request)
{
    $request->user()->currentAccessToken()->delete();
    return response()->json(['message' => 'Logged out']);
}
Enter fullscreen mode Exit fullscreen mode

Users expect logout to mean logged out. Don't leave valid tokens floating around.


Frequently Asked Questions

Is Sanctum secure enough for production?

Yes. Sanctum is maintained by the Laravel core team and used by thousands of production applications. It follows security best practices, and tokens are hashed before storage. For session-based auth, it uses Laravel's battle-tested session system.

Can I migrate from Passport to Sanctum?

Yes. If you're only using personal access tokens and not OAuth2 features, migration is straightforward. See the migration guide above. The main work is updating your User model trait and token creation calls.

Which is better for mobile apps?

Sanctum. Unless your mobile app needs to authenticate with third-party services through OAuth2 flows you control, Sanctum's token-based authentication is simpler and fully sufficient. I use it for all my mobile app backends.

Is Passport overkill for my project?

Probably, if you're asking this question. Passport exists for OAuth2 server functionality. If you don't need to issue OAuth2 tokens to third-party applications, you don't need Passport. Use Sanctum.

What about JWT? Should I use tymon/jwt-auth instead?

For most Laravel applications, no. Sanctum handles token authentication cleanly without JWT complexity. JWTs have specific use cases (stateless auth, cross-service authentication), but they add complexity around token refresh, revocation, and security. Sanctum's approach (opaque tokens validated against the database) is simpler and works great for typical applications.

Can Sanctum handle multiple devices?

Yes. Each createToken() call creates a new token. Users can have tokens on their phone, tablet, and desktop simultaneously. You can even name tokens to help users identify devices when revoking access.


The Bottom Line

Here's my honest take after years of building Laravel applications:

Use Sanctum. It's the right choice for 90% of projects. Your SPA needs authentication. Your mobile app needs tokens. Your internal tools need API access. Sanctum handles all of this with minimal setup and zero OAuth2 headaches.

Use Passport only when you're building a platform where third-party developers create applications that access your users' data. That's a specific use case. Most of us aren't building the next Stripe or GitHub.

I've been there. Early in my Laravel journey, I'd reach for Passport because it seemed more "complete." More features must be better, right? Wrong. Features you don't use are complexity you maintain.

Sanctum exists because the Laravel team recognized that most API authentication doesn't need OAuth2. Trust their judgment. I do.

If your requirements change and you actually need OAuth2 later, migration is possible. But don't borrow complexity from a future that might never arrive.

Start simple. Use Sanctum.


Related Reading:

Official Documentation:

Top comments (0)