Laravel 13 and Sanctum: Secure Your REST API with Access Tokens
With php artisan install:api you get Sanctum and the token migration ready in a minute; with one trait and one middleware, your Laravel 13 REST API is protected by Bearer tokens. Here is how, step by step.
What Sanctum Is and When to Use It in Laravel 13
Sanctum is Laravel's first-party package for lightweight authentication. It was built to solve two different problems with a single installation: issuing access tokens for external consumers (mobile apps, third-party services, separate frontends) and letting your own SPA authenticate with cookies, like a classic session. In Laravel 13 it is installed as a standalone package, just like in the 12 series, and the official 13.x documentation describes it as the recommended way to secure your own APIs.
API Tokens vs. SPA Authentication
The difference comes down to who consumes the API and from where. An access token is an opaque string that the client sends with every request; it is ideal for mobile apps and for third parties that cannot manage cookies with you. SPA authentication, on the other hand, uses session cookies with CSRF protection and is designed for your own frontend, served from the same domain or a subdomain. Choosing the wrong one is behind most of the CORS issues you see in production.
Sanctum vs. Passport in 2026: When You Need OAuth
Passport implements full OAuth2: third-party clients, refresh tokens, and authorization flows designed so other applications can access your users' data with their explicit consent. Sanctum is lighter and covers most cases: an API you control whose consumers you know. The practical rule in 2026 is still the same: if third parties need to register as OAuth clients, use Passport; if you just want to protect your API, use Sanctum.
Installation: php artisan install:api
In Laravel 12 and 13, API routes no longer ship by default on fresh installs. The php artisan install:api command enables them in one step: it installs Sanctum, creates the routes/api.php file, and publishes the migration that creates the personal_access_tokens table. After running it, all that is left is to run your migrations.
php artisan install:api
php artisan migrateWhat the Command Creates: routes/api.php and the personal_access_tokens Migration
The routes/api.php file arrives with an example route and the /api prefix already applied. The personal_access_tokens migration stores each token with its name, its abilities (as JSON), and the expiration and last-used fields. Laravel only stores the SHA-256 hash of the token, never the plain value, so a stolen database does not expose reusable tokens.
Your First Token: the User Model and the HasApiTokens Trait
For a user to issue tokens, the model representing them must use the HasApiTokens trait. It is the only mandatory change to the model: the trait adds the relationship to tokens and the methods to create and revoke them.
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
}createToken() and the plainTextToken You Only See Once
Creating a token is one line. The createToken() method returns an object whose plainTextToken attribute holds the real string, and that value is only shown in this response: Laravel stores just its hash. That is why the typical mobile login flow returns that token to the client and asks it to store it securely (for example, in the system keychain).
$token = $user->createToken('mobile-app')->plainTextToken;
return response()->json(['token' => $token]);Protecting Routes with auth:sanctum
Once the user has a token, protecting routes is a matter of adding the sanctum guard to the auth middleware. The usual approach is to group the private routes and apply the middleware to the whole group.
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', fn (Request $request) => $request->user());
Route::get('/orders', [OrderController::class, 'index']);
});Sending the Bearer Token from the Client
The client sends the token in the Authorization header with the Bearer prefix. From the command line you can test it like this:
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-domain.com/api/userIf the token is valid, $request->user() returns the authenticated user; if it is missing or invalid, the response is a 401. The mobile app must add the same header to every request, usually through an HTTP client interceptor.
Abilities: Per-Token Permissions
A token can carry abilities, which are optional permissions that limit what that specific token can do. It is the way to give each device or integration only the minimal access it needs. In the store example, the delivery app could have a token with the orders:read ability and no access to payment data.
$token = $user->createToken('delivery-app', ['orders:read'])->plainTextToken;Checking Permissions with tokenCan()
Inside a controller you can ask the authenticated user whether the token they arrived with has a specific ability. tokenCan() looks at the current token's abilities, not the user's in general, which allows shared routes with different behavior depending on the client.
if ($request->user()->tokenCan('orders:read')) {
// return orders
}Token Expiration with expires_at
You can make a token expire automatically by passing an expiration date to createToken(); the guard will reject it after that date. You can also revoke tokens at any time, for example on mobile logout, by deleting the user's tokens.
$token = $user->createToken('mobile-app', ['*'], now()->addDays(30))->plainTextToken;
// logout: revoke all of the user's tokens
$user->tokens()->delete();SPA Authentication: Cookies, CSRF, and CORS
If the consumer is your own SPA (Vue, React, or Livewire on the same domain), Sanctum offers SPA mode: instead of tokens, the login returns a session cookie protected against CSRF, and the guard validates the session like in a classic app. To enable it you must configure the SPA domain in config/sanctum.php, use the stateful middleware, and allow credentials in CORS.
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1')),When NOT to Use API Tokens: Your Own First-Party SPA
The official documentation recommends SPA mode for your own frontend: session cookies are safer than keeping a token in localStorage (less surface for XSS theft) and you do not have to manage expirations by hand. Tokens are left for what they are for: mobile apps, third-party integrations, and any client that cannot maintain a session cookie with you.
Common Mistakes That Break Your First Request
The most common one is forgetting the Authorization header or writing the prefix wrong (Bearer with a capital B and a space). Next comes skipping php artisan migrate, so the tokens table does not exist and Sanctum returns a database error. In SPAs, the classic failure is misconfigured CORS: supports_credentials is missing or the SPA domain is not in stateful. And one detail that costs hours: if you regenerate the token on every login, the old client loses access; store the token on the client and reuse it until it expires or is revoked.
Conclusion
Securing a REST API in Laravel 13 with Sanctum comes down to four pieces: the install command, the trait on the model, the middleware on the routes, and the Bearer header on the client. With abilities and expiration you control what each token can do and for how long, and with SPA mode you also cover your own frontend without leaving the same package. If you are building your first Laravel 13 API, this is the starting point; the natural next step is exploring eager loading so your endpoints do not suffer the N+1 problem.