Token-based authentication — Sanctum for SPA and API tokens
Concept
Laravel Sanctum provides two distinct authentication mechanisms in a single lightweight package: SPA cookie-based authentication for first-party JavaScript frontends, and API token authentication for third-party consumers and mobile apps. The choice between them is architectural, not an either/or — a single Laravel app can serve both simultaneously.
For SPA authentication, Sanctum acts as a thin wrapper over the standard Laravel session guard. The SPA calls GET /sanctum/csrf-cookie to receive a CSRF cookie and X-XSRF-TOKEN header setup, then submits credentials to a normal login endpoint. Sanctum's EnsureFrontendRequestsAreStateful middleware detects requests originating from the configured stateful domains and routes them through the web guard (session-based) rather than the token guard. This means the SPA gets all the benefits of session auth — CSRF protection, HttpOnly cookies, automatic logout on session expiry — without any token management in JavaScript.
For API token authentication, Sanctum stores plain API tokens in a personal_access_tokens table. $user->createToken('token-name') returns a NewAccessToken object whose plainTextToken property is the raw token — shown only once. Subsequent requests send this token as Authorization: Bearer <token>. Sanctum hashes it with SHA-256 before comparing against the stored hash, so the database never contains the raw token. Token abilities (scopes) are stored as a JSON array: createToken('mobile', ['posts:read', 'posts:write']) and enforced with $request->user()->tokenCan('posts:write') or the abilities:posts:write middleware.
The key architectural difference from Passport is complexity: Sanctum requires no OAuth2 server, no client/secret management, and no token refresh flow. It is the right choice for first-party SPAs and simple API consumers. Passport is warranted when you need to issue tokens to third-party OAuth clients, need authorization code flow, or need token introspection endpoints.
| Feature | Sanctum SPA mode | Sanctum token mode | Passport |
|---|---|---|---|
| Session cookie | Yes | No | No |
| Token in DB | No | Yes (hashed) | Yes (encrypted) |
| CSRF protection | Yes | No | No |
| Abilities/scopes | N/A | Yes (JSON array) | Yes (OAuth scopes) |
| OAuth2 flows | No | No | Yes |
Code Example
<?php
// --- Installation ---
// php artisan vendor:publish --tag=sanctum-config
// php artisan migrate (creates personal_access_tokens table)
// --- Issuing a token ---
use App\Models\User;
$user = User::find(1);
// Simple token
$token = $user->createToken('my-app-token');
$plainText = $token->plainTextToken; // shown ONCE, not storable in DB
// Token with abilities
$token = $user->createToken('mobile-app', ['posts:read', 'profile:update']);
// --- Revoking tokens ---
$user->tokens()->delete(); // revoke ALL tokens
$user->currentAccessToken()->delete(); // revoke the token for THIS request
// --- Protecting routes ---
// routes/api.php
use Illuminate\Http\Request;
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', fn (Request $request) => $request->user());
Route::post('/posts', function (Request $request) {
// Ability check
if (! $request->user()->tokenCan('posts:write')) {
abort(403, 'Token lacks posts:write ability.');
}
// ...
});
});
// Middleware shorthand for ability enforcement
Route::post('/posts', PostController::class)
->middleware(['auth:sanctum', 'abilities:posts:write']);
// --- SPA CSRF setup (in config/sanctum.php) ---
// 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,localhost:3000')),Interview Q&A
Q: How does Sanctum's SPA mode differ from issuing an API token, and when should you use each?
SPA mode uses the existing Laravel session infrastructure — the SPA authenticates via a normal login POST, receives session cookies and a CSRF cookie, and every subsequent request is authenticated by the web guard reading the session. There are no tokens in the database. API token mode creates a row in personal_access_tokens and requires the client to send Authorization: Bearer <token> on every request; the server hashes the received token and does a database lookup on every request. Use SPA mode for your own JavaScript frontend (same domain or configured stateful domain) — it is more secure because credentials never leave the server context. Use token mode for mobile apps, third-party integrations, or when you need ability-scoped access control on a per-token basis.
Q: How does Sanctum hash and verify API tokens, and why does it never store the plain token?
createToken() generates a cryptographically random 40-character string using Str::random(40), prepends the token's database id separated by a pipe ({id}|{token}), and returns the combined string as plainTextToken. The raw random portion is then hashed with SHA-256 via hash('sha256', $token) and stored in the token column. On incoming requests, Sanctum splits the bearer token on |, uses the first part as the record id for a direct primary-key lookup (avoiding a full-table search), and then hashes the second part to compare against the stored hash with hash_equals(). Storing only the hash means a database breach does not expose usable tokens.
Q: What is the EnsureFrontendRequestsAreStateful middleware and why does Sanctum need it?
This middleware inspects the Origin or Referer header of incoming requests and checks whether the origin matches one of the configured stateful domains in config/sanctum.php. If it matches, the middleware adds the web session middleware stack (reading sessions, encrypting cookies, handling CSRF) to the request pipeline so the request is processed as a stateful session request. Without it, every request through auth:sanctum would be treated as a token request and session-based authentication would be ignored. The middleware effectively makes the api middleware group stateful for requests from trusted first-party origins.