
Most Laravel Sanctum tutorials hand you a createToken() snippet and call it authentication. Then you wire it into a React or Vue frontend on the same domain, and you have quietly built the wrong thing - Sanctum's own docs tell you not to use API tokens to authenticate your own first-party SPA.
We run Sanctum in production across our own SaaS backends - including a multi-tenant platform where users live on per-tenant subdomains, and an internal tool that mints Sanctum tokens against a non-User model and rotates them on a schedule. The single decision that determines whether Sanctum is effortless or a week of CORS debugging is the one most guides skip: which of Sanctum's two modes you actually need.
This guide leads with that, then gives you the current, correct setup for each.
Key Features of Laravel Sanctum
Here are some of the standout features of Laravel Sanctum that highlight why you should consider using this tool to simplify authentication.
Token-Based Authentication
Sanctum allows users to issue API tokens without the complexity of OAuth. These tokens can be scoped, restricting the actions they can perform. This is particularly useful for managing API access for different parts of your application or for external services.
SPA Authentication
For SPAs, Sanctum uses cookie-based session authentication, which allows your JavaScript front end to authenticate using the same Laravel session cookies. This approach provides a seamless and secure authentication mechanism for SPAs.
CSRF Protection
Sanctum offers robust CSRF protection for your application. When using Sanctum, your API requests are protected against CSRF attacks, ensuring the security of your application.
How do I install Sanctum in Laravel (2026)?
On Laravel 11, 12, and 13 there is a single Artisan command that installs Sanctum, publishes its config, and adds the migration:
php artisan install:api
Run your migrations, and Sanctum is ready. If you are authenticating an SPA, also complete the SPA configuration section below.
Using Sanctum for API Token Authentication
To issue a token to a user, first, you need to update the User model:
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
Creating token:
$token = $request->user()->createToken($request->token_name);
return ['token' => $token->plainTextToken];
Revoking tokens:
// Revoke all tokens...
$user->tokens()->delete();
// Revoke the token that was used to authenticate the current request...
$request->user()->currentAccessToken()->delete();
// Revoke a specific token...
$user->tokens()->where('id', $tokenId)->delete();
Token Scopes
Tokens can have scopes that define their permissions. When creating a token, you can specify the scopes:
$token = $user->createToken('token-name', ['view-posts', 'create-posts'])
->plainTextToken;
You can then check the token’s scopes in your routes or controllers:
if ($user->tokenCan('create-posts')) {
// ...
}
Protecting Routes with Laravel Sanctum
Sanctum provides powerful middleware to check incoming requests if it is authenticated and have the token ability:
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'abilities' => CheckAbilities::class,
'ability' => CheckForAnyAbility::class,
]);
})
To protect routes, you should use the auth:sanctum and ability (abilities) middleware:
Route::middleware(['auth:sanctum', 'abilities: create-post'])
->post('/posts/create', function (Request $request) {
//
});
The difference between ability and abilities is quite simple:
- ability middleware checks if the token has at least one ability
- abilities middleware checks if the token has every listed ability
SPA Authentication
For SPAs, you need to set up session-based authentication. Typically, you will have a login route that your SPA will use to authenticate:
Route::post('/login', function (Request $request) {
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return response()->json(['message' => 'Logged in successfully']);
}
return response()->json(['message' => 'Invalid credentials'], 401);
});
After logging in, your SPA can make authenticated requests using the session cookie.
Handling in frontend
When issuing a request for the sanctum route, first, you need to make a request on /sanctum/csrf-cookie to init CSRF protection:
axios.get('/sanctum/csrf-cookie').then(response => {
axios.post('/login', {
email: 'user@example.com',
password: 'password'
}).then(response => {
// Handle successful authentication
}).catch(error => {
// Handle authentication failure
});
});
The gotcha that eats a week (from our own production)
When SPA auth "just returns 401/419 and no one knows why," it is almost always one of these, and we have hit each in real Redberry projects.
- A stateful-domain / CORS / session-domain mismatch. All four settings above have to agree. If
supports_credentialsisfalse, or the SPA origin is not instateful, or the sessiondomainis missing its leading dot, the browser silently drops the cookie, and every request looks logged out. Change all four together, then clear cookies before retesting. - A 419 on POST means the
X-XSRF-TOKENheader is missing or not URL-decoded - you skipped the/sanctum/csrf-cookiecall orwithXSRFTokenis off. - Multi-tenant subdomains: with users on per-tenant subdomains, the session cookie
domainmust cover the parent (.example.com) or a tenant cannot stay logged in across its own subdomain. We ship exactly this pattern in production, and it is invisible until it breaks. - Laravel Octane: long-lived workers can leak container state between requests; if you run Sanctum's stateful middleware under Octane, treat per-request auth state as something to reset, not to assume fresh.
[VERIFY]behavior against your Octane + Sanctum versions. - Tokens on a non-
Usermodel:HasApiTokensworks on any Eloquent model, not justUser. We mint Sanctum tokens on a repository/integration model and rotate them, leaning on Sanctum'sexpires_atto keep the old token valid during a short grace-period overlap so nothing breaks mid-rotation.[VERIFY]Exact Sanctum major version that introduced nativeexpires_at.
Sanctum vs Passport vs the Laravel starter kits - which do I need?
- Building your own first-party web app UI (login, register, password reset)? You probably do not touch Sanctum's token features directly at all - use a Laravel starter kit (React, Vue, Svelte, or Livewire), which uses Laravel Fortify for session auth out of the box. WorkOS AuthKit-powered variants add social login, passkeys, and SSO.
- Need API tokens or a decoupled SPA? Use Sanctum - this guide.
- Need full OAuth2 (you are an identity provider issuing tokens to third parties with authorization codes, refresh tokens, etc.)? Use Laravel Passport. Sanctum is deliberately not OAuth.For the overwhelming majority of SPA + API apps, Sanctum is the right, lightest-weight answer.
Testing Sanctum
Authenticate a fake user (with abilities) in tests without minting a real token:
use Laravel\Sanctum\Sanctum;
Sanctum::actingAs(User::factory()->create(), ['view-tasks']);
// use ['*'] to grant all abilities
Final Thoughts on Laravel Sanctum
Laravel Sanctum provides a simple yet powerful solution for API token management and SPA authentication. Its ease of use, combined with Laravel’s robust features, makes it an excellent choice for developers looking to implement secure authentication mechanisms. It doesn’t matter whether you’re building an API, a mobile app, or a SPA; Laravel Sanctum offers the flexibility and simplicity you need to manage authentication effectively.







