DEV Community

Cover image for The Laravel Sanctum setup that actually works (and what trips most people up)
K. Polash
K. Polash

Posted on

The Laravel Sanctum setup that actually works (and what trips most people up)

I've set up Sanctum in probably 8 or 9 Laravel projects at this point — payroll systems, SaaS tools, e-commerce APIs. Every time I onboard a junior dev or review someone's code, I see the same 3-4 mistakes repeated. So here's the setup that works, plus the exact things that will silently break it.


First: what Sanctum actually does

Sanctum issues a plain-text token, stores a hashed version in your personal_access_tokens table, and validates it on every request with a database lookup. That's it. No JWT, no signatures, no decoding — just a DB row.

This is why token revocation is trivially easy with Sanctum and nightmarish with JWT. Delete the row, the token is dead instantly.


The setup (Laravel 12)

Step 1 — Install (Laravel 10 and below only)

Laravel 11 and 12 ship with Sanctum already. If you're on 10:

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Step 2 — Add the trait to your User model

This is the one people forget most often:

use Laravel\Sanctum\HasApiTokens;

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

Without this, createToken() doesn't exist on your model, and you'll get a cryptic error.

Step 3 — Routes

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

    // Public
    Route::post('auth/register', [AuthController::class, 'register']);
    Route::post('auth/login',    [AuthController::class, 'login']);

    // Protected
    Route::middleware('auth:sanctum')->group(function () {
        Route::post('auth/logout', [AuthController::class, 'logout']);
        Route::get('auth/me',     [AuthController::class, 'me']);
    });

});
Enter fullscreen mode Exit fullscreen mode

Step 4 — The AuthController

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);
    }

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

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

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

Step 5 — Send the token

Authorization: Bearer 1|yourtokenhere...
Enter fullscreen mode Exit fullscreen mode

Note the format — 1|randomstring. The number is the token ID in the database. Send the full string.


The 4 things that will silently break your setup

1. Forgetting HasApiTokens on the User model
You'll get a "method not found" error on createToken(). Always check this first.

2. Sending the token wrong
It must be Authorization: Bearer YOUR_TOKEN. Not a query param, not a cookie, not Token YOUR_TOKEN. Bearer, exactly.

3. Not running migrations
The personal_access_tokens table doesn't exist yet. Run php artisan migrate and confirm the table is there.

4. CORS blocking your frontend
Add your frontend URL to config/cors.php:

'allowed_origins'      => ['https://yourfrontend.com'],
'supports_credentials' => true,
Enter fullscreen mode Exit fullscreen mode

Revoking tokens

This is where Sanctum shines vs JWT:

// Logout current device
$request->user()->currentAccessToken()->delete();

// Logout all devices
$user->tokens()->delete();
Enter fullscreen mode Exit fullscreen mode

With JWT, you'd need a denylist and extra infrastructure. With Sanctum, it's one line.


Setting token expiry

In config/sanctum.php:

// Expire after 60 days (value is in minutes)
'expiration' => 60 * 24 * 60,

// Never expire
'expiration' => null,
Enter fullscreen mode Exit fullscreen mode

Should you use Sanctum or JWT?

Use Sanctum if you're building an API for your own app (SPA, mobile). Use JWT only if you need stateless cross-service auth in a microservices setup. For 90% of Laravel projects, Sanctum is the right call.

I wrote a full breakdown here: Does Laravel Sanctum use JWT? →


Full tutorial

If you want the complete version with register, me endpoint, token abilities, and more — I wrote it up here:

👉 Laravel Sanctum Setup Tutorial (2026)


Already using Sanctum?

I built a Laravel API Starter Kit with Sanctum pre-configured — auth, roles, pagination, API versioning all included. Saves a few hours on every new project. $19 on Gumroad if you want to check it out.


What issues have you run into with Sanctum? Drop them below — happy to help debug.

Top comments (0)