How Does Laravel Sanctum Work?
Laravel Sanctum operates through a straightforward token-based authentication flow. Here's how it works under the hood:- User Authentication: After successful login or registration, your controller creates a token using
$user->createToken('token-name'). This generates a secure, random string (the plain-text token) and stores a hashed version in thepersonal_access_tokenstable along with the user ID, token name, abilities (if any), and expiration (optional). - Token Transmission: The plain-text token is returned to the client (e.g., in a JSON response:
{"access_token": "1|randomstringhere"}). The client includes it in future requests via theAuthorization: Bearer {token}header. - Request Verification: Incoming API requests hit the
auth:sanctummiddleware. Sanctum extracts the Bearer token, looks up the hashed version in the database, verifies it matches, checks expiration/abilities, and—if valid—logs in the associated user for that request (making$request->user()available). - Token Revocation: Users can revoke tokens (e.g., on logout) with
$user->tokens()->delete()or individually via$token->delete(). Expired tokens are automatically pruned using Laravel's scheduler (sanctum:prune-expiredcommand).
For SPAs on the same domain, Sanctum can also use cookie-based auth (with CSRF tokens) for even smoother integration—no manual Bearer headers needed in some cases. The entire system is stateless from the client's perspective while remaining database-backed for security and revocation control.
This elegant design makes Sanctum fast, secure, and easy to debug—explaining why it's still the preferred method for Laravel REST API authentication in 2026. (Word count: 298)

Table Of Content
1 Prerequisites
- PHP 8.2+ and Composer installed.
- MySQL or another database.
- Postman for testing API endpoints.
- Node.js/npm if you plan to integrate with a frontend (not required here).
2 Introduction
In this comprehensive 2026 tutorial, you'll master Laravel Sanctum for secure REST API authentication by building a fully functional application from scratch. We'll walk through every step to implement token-based authentication in Laravel 11, including project setup with the modern php artisan install:api command, user registration & login, protected routes, token management, and a practical Blog CRUD example. By following this Laravel 11 Sanctum REST API authentication guide, you'll gain the skills to create production-ready, secure APIs for SPAs, mobile apps, or any token-driven backend—effortlessly and with best practices in mind.
3 Create / Install a Laravel Project
3.1 Install Laravel Project
composer create-project laravel/laravel laravel11-sanctum-api
Then, navigate to your project directory:
cd laravel11-sanctum-api
3.2 Configure MySql Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_sanctum
DB_USERNAME=root
DB_PASSWORD=
4 Install & Configure Sanctum
php artisan install:api --sanctum
This:
- Installs Sanctum.
- Publishes config and migrations.
- Adds the api middleware alias in bootstrap/app.php.
- Sets up CORS in config/cors.php (configure as needed for your frontend origins).
Run migrations:
php artisan migrate
Best Practice: Token Pruning
Add this to your scheduler in app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('sanctum:prune-expired --hours=24')->daily();
}
Run scheduler in development: php artisan schedule:run. 5 Add HasApiTokens in User Model
<?php
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}
6 Create Controller - AuthController
php artisan make:controller Api/AuthController
In AuthController.php, define three functions: register, login, and logout.
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$token = $user->createToken('api-token', ['server:update'])->plainTextToken; // Example with ability
return response()->json([
'message' => 'User registered successfully',
'user' => $user,
'token' => $token,
], 201);
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
$token = $user->createToken('api-token', ['server:update'])->plainTextToken; // Example with ability
return response()->json([
'message' => 'Login successful',
'user' => $user,
'token' => $token,
], 200);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete(); // Revoke current token
// Or $request->user()->tokens()->delete(); // Revoke all tokens
return response()->json(['message' => 'Logged out successfully'], 200);
}
}
?>
7 Create Blog Model, Migration, and Controller (CRUD Example)
php artisan make:model Blog -m
In the migration file (e.g., database/migrations/xxxx_create_blogs_table.php):
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('blogs', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('blogs');
}
};
After creating the migration, run:
php artisan migrate
Next, create a Blog model at app/Models/Blog.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Blog extends Model
{
use HasFactory;
protected $fillable = ['title', 'description', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
Create the BlogController:
php artisan make:controller Api/BlogController --api --model=Blog
In app/Http/Controllers/Api/BlogController.php:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Blog;
use Illuminate\Http\Request;
class BlogController extends Controller
{
public function index()
{
return Blog::with('user')->paginate(10);
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'description' => 'required|string',
]);
$blog = $request->user()->blogs()->create($request->all());
return response()->json($blog, 201);
}
public function show(Blog $blog)
{
return $blog->load('user');
}
public function update(Request $request, Blog $blog)
{
$this->authorize('update', $blog); // Add policy if needed
$request->validate([
'title' => 'sometimes|required|string|max:255',
'description' => 'sometimes|required|string',
]);
$blog->update($request->all());
return $blog;
}
public function destroy(Blog $blog)
{
$this->authorize('delete', $blog); // Add policy if needed
$blog->delete();
return response()->json(null, 204);
}
}
8 Define API Endpoints in routes/api.php
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\BlogController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout']);
Route::apiResource('blogs', BlogController::class);
Route::get('/user', function (Request $request) {
return $request->user();
});
});
9 Folder Structure
The folder structure will look like this:
10 Testing the API
php artisan serve
Use Postman:
1. Register: POST http://127.0.0.1:8000/api/registerBody (JSON):
{
"name": "Test User",
"email": "test@example.com",
"password": "password",
"password_confirmation": "password"
}
Response: Includes user and token.
2. Login: POST http://127.0.0.1:8000/api/loginBody (JSON):
{
"email": "test@example.com",
"password": "password"
}
Response: Includes token.
3. Protected Route (Get User): GET http://127.0.0.1:8000/api/userHeaders: Authorization: Bearer {token_from_login}
4. Create Blog: POST http://127.0.0.1:8000/api/blogs
Headers: Authorization: Bearer {token_from_login} Body (JSON):
{
"title": "My First Blog",
"description": "This is a test blog post."
}
5. Logout: POST http://127.0.0.1:8000/api/logoutHeaders: Authorization: Bearer {token_from_login}
For other CRUD operations, use standard REST verbs on /api/blogs and /api/blogs/{id}.
11 Conclusion: Secure Your Laravel APIs with Sanctum Today
Congratulations! You've now built a complete, secure Laravel 11 Sanctum REST API authentication system from the ground up. Starting with a fresh Laravel 11 project, installing Sanctum via the streamlined install:api Artisan command, configuring the User model with the HasApiTokens trait, creating authentication endpoints for register/login/logout, protecting routes with auth:sanctum middleware, and implementing a full Blog CRUD resource—you've covered the essential flow every modern Laravel API developer needs in 2026.
Laravel Sanctum continues to shine as the lightweight, powerful choice for most API authentication scenarios. It delivers simple personal access tokens with optional abilities (scopes), efficient database-backed storage, easy revocation, automatic expiration pruning, and seamless integration for both token-based (mobile/third-party) and cookie-based (first-party SPA) use cases—without the complexity of full OAuth2 like Laravel Passport. This makes it ideal for the majority of projects: mobile backends, React/Vue/Next.js frontends, Flutter apps, or any RESTful service requiring quick, reliable security.
Key takeaways from this Laravel 11 Sanctum tutorial:
- Use
createToken()to issue secure, revocable tokens and return the plain-text value only once. - Protect sensitive routes with
auth:sanctummiddleware and check abilities usingtokenCan()for fine-grained control. - Schedule the
sanctum:prune-expiredcommand to automatically clean up old tokens and keep your database lean. - Always enforce HTTPS in production, configure proper CORS (especially for SPAs), and combine with rate limiting for robust defense.
- Test thoroughly with tools like Postman—verify tokens, protected endpoints, and logout behavior to catch issues early.
With these foundations, you're ready to expand: add role-based access (via Gates/Policies), implement refresh tokens if needed, integrate social logins with Laravel Socialite, or scale to microservices. Sanctum's simplicity doesn't compromise security—it's battle-tested in countless production apps and backed by Laravel's active ecosystem.
Ready to level up? Check the official Laravel Sanctum documentation for advanced features, explore our other Laravel tutorials here on GetSampleCode, or drop a comment below with your questions/results. Happy coding, and build something awesome with Laravel 11 and Sanctum!
Written by Revathi M
PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel
Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.
Frequently Asked Questions
You need PHP 8.2 or higher, Composer, and MySQL. A fresh Laravel 11 project is recommended.
Run php artisan install:api. This installs Sanctum and sets up API scaffolding.
Add the HasApiTokens trait: use Laravel\\\\Sanctum\\\\HasApiTokens; and include it in the class: use HasApiTokens;
After successful registration or login, use $user->createToken('auth_token')->plainTextToken to generate a Bearer token.
Wrap routes in Route::middleware('auth:sanctum')->group() in routes/api.php.
POST /api/register, POST /api/login, POST /api/logout (protected), and GET /api/user (protected for current user).
Include it in the Authorization header: Authorization: Bearer
In the logout method, call Auth::user()->tokens()->delete() to revoke all tokens for the user.
Common issues: Missing or invalid Bearer token, not using auth:sanctum middleware, or invalid credentials during login. Check headers and token validity.
This tutorial focuses on pure API token authentication (not SPA cookies). It uses plain text tokens for REST APIs.
