Laravel Sanctum & API Authentication
Laravel Sanctum provides a lightweight authentication system for SPAs (single page applications), mobile applications, and simple token-based APIs. It offers a simple way to issue API tokens to users and authenticate incoming requests without the complexity of OAuth.
What is Laravel Sanctum?
Sanctum offers two distinct features:
- SPA Authentication: Session-based authentication for single page applications built with frameworks like Vue, React, or Inertia
- API Token Authentication: Token-based authentication for mobile applications and third-party API consumers
Note: Sanctum does NOT use OAuth tokens. Instead, it uses simple personal access tokens stored in your database, making it much simpler to implement and understand.
Installation and Setup
Laravel includes Sanctum by default in Laravel 8+. For older versions, install via Composer:
# Install Sanctum (if not already included)
composer require laravel/sanctum
# Publish the configuration and migration files
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
# Run migrations to create personal_access_tokens table
php artisan migrate
Add Sanctum's middleware to your API middleware group in app/Http/Kernel.php:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
protected $middlewareGroups = [
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
}
API Token Authentication
Use the HasApiTokens trait in your User model:
<?php
namespace App\Models;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
}
Issuing API Tokens
Create an endpoint that issues tokens after authenticating users:
<?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 login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
// Create a token for the user
$token = $user->createToken($request->device_name)->plainTextToken;
return response()->json([
'token' => $token,
'user' => $user,
]);
}
public function logout(Request $request)
{
// Revoke the current user's token
$request->user()->currentAccessToken()->delete();
return response()->json([
'message' => 'Logged out successfully',
]);
}
public function logoutAll(Request $request)
{
// Revoke all tokens for the user
$request->user()->tokens()->delete();
return response()->json([
'message' => 'All devices logged out successfully',
]);
}
}
Token Abilities (Permissions)
Assign specific abilities to tokens for fine-grained access control:
<?php
// Create a token with specific abilities
$token = $user->createToken('mobile-app', [
'post:create',
'post:update',
'post:delete',
])->plainTextToken;
// Create a read-only token
$token = $user->createToken('analytics', [
'post:read',
'user:read',
])->plainTextToken;
// In your controller, check if the token has a specific ability
public function store(Request $request)
{
if (!$request->user()->tokenCan('post:create')) {
return response()->json([
'message' => 'Unauthorized'
], 403);
}
// Create the post...
}
// Or use middleware
Route::post('/posts', [PostController::class, 'store'])
->middleware('ability:post:create');
Route::get('/posts', [PostController::class, 'index'])
->middleware('ability:post:read,post:create'); // Can have either ability
Tip: Token abilities are stored in the database, so you can create, check, and revoke them at any time without changing your code.
Protecting Routes
Use the auth:sanctum middleware to protect API routes:
<?php
// routes/api.php
use App\Http\Controllers\Api\PostController;
use App\Http\Controllers\Api\AuthController;
// Public routes
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::post('/logout', [AuthController::class, 'logout']);
Route::post('/logout-all', [AuthController::class, 'logoutAll']);
Route::apiResource('posts', PostController::class);
Route::apiResource('comments', CommentController::class);
});
Making Authenticated Requests
Include the token in the Authorization header when making API requests:
// JavaScript fetch example
fetch('https://api.example.com/api/user', {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN_HERE',
'Accept': 'application/json',
}
})
.then(response => response.json())
.then(data => console.log(data));
// Axios example
axios.get('/api/user', {
headers: {
'Authorization': `Bearer ${token}`,
}
})
.then(response => console.log(response.data));
// PHP cURL example
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.example.com/api/user');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer YOUR_API_TOKEN_HERE',
'Accept: application/json',
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
SPA Authentication
For single page applications running on the same domain, Sanctum uses Laravel's session cookies:
1. Configure CORS and Session
Update your config/cors.php:
<?php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:3000'], // Your SPA URL
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
2. Initialize CSRF Protection
Before logging in, fetch a CSRF token from the /sanctum/csrf-cookie endpoint:
// In your SPA (Vue, React, etc.)
// Step 1: Get CSRF cookie
await axios.get('/sanctum/csrf-cookie');
// Step 2: Make login request
await axios.post('/login', {
email: 'user@example.com',
password: 'password'
});
// Step 3: Make authenticated requests
const response = await axios.get('/api/user');
3. Configure Stateful Domains
In config/sanctum.php, specify which domains can make stateful API requests:
<?php
return [
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS',
sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
)
)),
'guard' => ['web'],
'expiration' => null,
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],
];
Warning: SPA authentication only works when your API and SPA are on the same top-level domain. For different domains, use API token authentication instead.
Token Management
Manage user tokens programmatically:
<?php
// Get all tokens for a user
$tokens = $user->tokens;
// Get the current token
$currentToken = $request->user()->currentAccessToken();
// Check token abilities
$canCreatePost = $currentToken->can('post:create');
$canDeletePost = $currentToken->can('post:delete');
// Revoke a specific token
$user->tokens()->where('id', $tokenId)->delete();
// Revoke all tokens
$user->tokens()->delete();
// Token expiration (set in config/sanctum.php)
'expiration' => 60, // Minutes
// Check if token is expired
if ($token->expires_at && now()->greaterThan($token->expires_at)) {
// Token is expired
}
Mobile Application Authentication
Implement a complete authentication flow for mobile apps:
<?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 MobileAuthController 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',
'device_name' => 'required|string',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$token = $user->createToken($request->device_name)->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
], 201);
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
// Optionally revoke existing tokens for this device
$user->tokens()->where('name', $request->device_name)->delete();
$token = $user->createToken($request->device_name)->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}
public function profile(Request $request)
{
return response()->json([
'user' => $request->user(),
'token_name' => $request->user()->currentAccessToken()->name,
]);
}
public function updateProfile(Request $request)
{
$request->validate([
'name' => 'sometimes|string|max:255',
'email' => 'sometimes|email|unique:users,email,' . $request->user()->id,
]);
$request->user()->update($request->only(['name', 'email']));
return response()->json([
'user' => $request->user(),
'message' => 'Profile updated successfully',
]);
}
}
Exercise 1: Basic API Authentication
Create a complete API authentication system:
- Install and configure Sanctum
- Create registration and login endpoints
- Create a protected
/api/profile endpoint
- Test with Postman or cURL
- Implement token revocation (logout)
Exercise 2: Token Abilities
Implement fine-grained permissions using token abilities:
- Create tokens with different abilities (admin, user, read-only)
- Protect routes using the
ability middleware
- Create an endpoint to list user's active tokens
- Allow users to revoke individual tokens
- Test that unauthorized abilities are rejected
Exercise 3: SPA Authentication
Set up session-based authentication for a Vue or React SPA:
- Configure CORS and stateful domains
- Create a login form that fetches CSRF token first
- Implement login, logout, and profile fetching
- Add axios interceptors to handle 401 responses
- Test that cookies are properly set and sent
Best Practices
- Use Device Names: Always require device_name when creating tokens for better tracking
- Set Expiration: Configure token expiration in config/sanctum.php for security
- Revoke on Password Change: Delete all tokens when users change passwords
- Use Abilities: Implement token abilities for fine-grained access control
- HTTPS Only: Always use HTTPS in production to protect tokens
- Rate Limiting: Apply rate limiting to authentication endpoints
- Log Token Activity: Track token creation and usage for security auditing
Summary
In this lesson, you learned:
- What Laravel Sanctum is and when to use it
- How to install and configure Sanctum
- Issuing and revoking API tokens
- Implementing token abilities for permissions
- Protecting routes with auth:sanctum middleware
- SPA authentication using session cookies
- Building authentication for mobile applications
- Best practices for secure token management
Sanctum provides a simple, secure authentication system perfect for modern applications. Choose API tokens for mobile apps and third-party integrations, and session-based authentication for SPAs on the same domain.