Laravel Sanctum is the official, lightweight API authentication package built into Laravel. It handles token issuance, route protection, and token revocation with minimal setup — making it the right choice for almost every Laravel REST API and mobile backend.

This tutorial covers a complete Sanctum setup from scratch: installation, User model configuration, protected routes, a full AuthController, and how to make authenticated requests. All examples work with Laravel 12.

What is Laravel Sanctum?

Sanctum provides API token authentication using plain-text tokens stored in a personal_access_tokens database table. Unlike JWT, tokens aren't self-contained — each request does a quick database lookup to validate. The trade-off is instant, reliable token revocation.

Feature Sanctum Passport JWT (tymon)
Best for Your own API OAuth2 / third-party Stateless / microservices
Token storage Database Database Client-side only
Setup complexity Simple Complex Medium
Built into Laravel ✓ Yes ✕ No ✕ No
Token revocation Easy ✓ Yes Hard
SPA cookie auth ✓ Yes ✕ No ✕ No

For most Laravel REST APIs, Sanctum is the right choice. Use Passport only when you need a full OAuth2 server for third-party developers.

Step 1: Install Laravel Sanctum

On Laravel 11 and 12, Sanctum is included by default. For Laravel 10 and below, install it manually:

terminal
# Laravel 10 and below — install manually
composer require laravel/sanctum

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

php artisan migrate

Laravel 11 / 12: Sanctum is already installed. Just run php artisan migrate if you haven't already — the personal_access_tokens table migration is included.

Step 2: Add HasApiTokens to the User Model

Add the HasApiTokens trait to your User model. This gives the model the createToken(), tokens(), and currentAccessToken() methods:

app/Models/User.php

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;

    // rest of your model...
}

Step 3: Set Up API Routes

Define your public and protected routes in routes/api.php. The auth:sanctum middleware guards any route that requires a valid token:

routes/api.php

php
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\UserController;

Route::prefix('v1')->group(function () {

    // Public routes — no token required
    Route::prefix('auth')->group(function () {
        Route::post('register', [AuthController::class, 'register']);
        Route::post('login',    [AuthController::class, 'login']);
    });

    // Protected routes — valid Sanctum token required
    Route::middleware('auth:sanctum')->group(function () {
        Route::post('auth/logout', [AuthController::class, 'logout']);
        Route::get('auth/me',     [AuthController::class, 'me']);
        Route::get('users',       [UserController::class, 'index']);
    });

});

Step 4: Build the AuthController

This controller handles registration, login, logout, and the authenticated user endpoint. The key line is createToken()->plainTextToken — this generates and returns the token the client will store and send on future requests:

app/Http/Controllers/Api/AuthController.php

php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $request->validate([
            'name'     => 'required|string|max:255',
            'email'    => 'required|email|unique:users',
            'password' => 'required|min:8|confirmed',
        ]);

        $user = User::create([
            'name'     => $request->name,
            'email'    => $request->email,
            'password' => Hash::make($request->password),
        ]);

        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            'user'  => $user,
            'token' => $token,
        ], 201);
    }

    public function login(Request $request)
    {
        $request->validate([
            'email'    => 'required|email',
            'password' => 'required',
        ]);

        if (! Auth::attempt($request->only('email', 'password'))) {
            return response()->json([
                'message' => 'Invalid credentials'
            ], 401);
        }

        $user  = Auth::user();
        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            'user'  => $user,
            'token' => $token,
        ]);
    }

    public function logout(Request $request)
    {
        $request->user()->currentAccessToken()->delete();

        return response()->json([
            'message' => 'Logged out successfully'
        ]);
    }

    public function me(Request $request)
    {
        return response()->json($request->user());
    }
}

Step 5: Make Authenticated Requests

After login, the client receives a plain-text token. Send it in the Authorization header as a Bearer token on every protected request:

HTTP Requests

http
# 1. Login — get your token
POST /api/v1/auth/login
Content-Type: application/json

{
  "email":    "user@example.com",
  "password": "yourpassword"
}

# Response
{
  "user":  { "id": 1, "name": "John", ... },
  "token": "1|abc123yourtokenhere..."
}

# 2. Use the token on protected requests
GET /api/v1/auth/me
Authorization: Bearer 1|abc123yourtokenhere...

Token format: Sanctum tokens look like 1|randomstring. The number before the pipe is the token ID in the database. Always send the full string including the numeric prefix.

Token Abilities (Optional Scopes)

Sanctum supports token abilities — a simple alternative to OAuth scopes. You can restrict what each token is allowed to do:

Token abilities

php
// Issue a token with specific abilities
$token = $user->createToken('mobile-app', ['read', 'write'])->plainTextToken;

// Check ability in a controller
if ($request->user()->tokenCan('write')) {
    // allowed
}

// Or enforce via middleware
Route::middleware(['auth:sanctum', 'ability:write'])->group(function () {
    Route::post('posts', [PostController::class, 'store']);
});

Revoking Tokens

Because tokens are stored in the database, revocation is instant. This is one of Sanctum's biggest advantages over JWT — no denylist needed, just delete the row:

Revocation options

php
// Revoke the current token (standard logout)
$request->user()->currentAccessToken()->delete();

// Revoke all tokens — logout from every device
$user->tokens()->delete();

// Revoke tokens by name
$user->tokens()->where('name', 'mobile-app')->delete();

Setting Token Expiry

Set the expiration value in config/sanctum.php. The value is in minutes:

config/sanctum.php

php
// Expire tokens after 60 days
'expiration' => 60 * 24 * 60,

// Tokens that never expire
'expiration' => null,

Common Issues & Fixes

401 Unauthenticated on every request

Make sure you're sending the token as Authorization: Bearer YOUR_TOKEN — not as a query parameter or cookie. Also confirm the HasApiTokens trait is on your User model and migrations have run.

CORS errors from a frontend SPA

Add your frontend URL to config/cors.php:

config/cors.php

php
'allowed_origins'      => ['https://yourfrontend.com'],
'supports_credentials' => true,

Missing personal_access_tokens table

Run php artisan migrate and confirm the table exists in your database. On Laravel 11/12 this migration is included automatically.


Frequently Asked Questions

Does Laravel Sanctum use JWT tokens?

No. Sanctum uses plain-text tokens stored in your database, not JWT. JWT tokens are stateless and self-contained; Sanctum tokens require a database lookup on each request. The trade-off is that Sanctum tokens are much easier to revoke instantly. See the full breakdown in Laravel Sanctum vs JWT.

When should I use Sanctum vs Passport?

Use Sanctum when you're building an API for your own frontend — SPA or mobile app. Use Passport only when you need a full OAuth2 server to issue tokens to third-party developers. Passport is significantly more complex to set up and maintain.

Yes — Sanctum also supports cookie-based SPA authentication using CSRF tokens. For a REST API or mobile backend, token-based authentication as shown in this tutorial is the standard approach.

Work with me

Need help with your Laravel architecture?

I build clean, maintainable Laravel backends for SaaS products — REST APIs, queue systems and production patterns like the one in this article.