Understanding Laravel Sanctum
Laravel Sanctum provides a lightweight authentication system for SPAs (Single Page Applications), mobile applications, and simple token-based APIs. It's perfect for authenticating APIs without the complexity of OAuth2.
What is Sanctum?
Sanctum offers two authentication methods:
- API Tokens: Simple token-based authentication for mobile apps and third-party consumers
- SPA Authentication: Cookie-based authentication for JavaScript SPAs running on the same domain
Note: Sanctum is ideal for most APIs. Use Passport only if you need full OAuth2 server implementation with multiple grant types.
Installing Sanctum
Install Sanctum via Composer:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
This creates the personal_access_tokens table to store API tokens.
Configuring Sanctum
Add Sanctum middleware to app/Http/Kernel.php:
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Add the HasApiTokens trait to your User model:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
// ... rest of model
}
Creating API Tokens
Generate tokens for users after authentication:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
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 token
$token = $user->createToken($request->device_name)->plainTextToken;
return response()->json([
'success' => true,
'message' => 'Login successful',
'data' => [
'user' => $user,
'token' => $token,
]
]);
}
Tip: The device_name helps users identify which device or application a token belongs to.
Protecting Routes with Sanctum
Apply the auth:sanctum middleware to protect routes:
// routes/api.php
use Illuminate\Support\Facades\Route;
// Public routes
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::post('/logout', [AuthController::class, 'logout']);
Route::apiResource('posts', PostController::class);
});
Making Authenticated Requests
Include the token in the Authorization header:
# Using cURL
curl -H "Accept: application/json" \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
https://api.example.com/api/user
# Using JavaScript Fetch
fetch('https://api.example.com/api/user', {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + token
}
})
Revoking Tokens
Allow users to logout by revoking their current token:
public function logout(Request $request)
{
// Revoke current token
$request->user()->currentAccessToken()->delete();
return response()->json([
'success' => true,
'message' => 'Logged out successfully'
]);
}
Revoke all tokens for a user:
// Revoke all tokens
$user->tokens()->delete();
// Revoke specific token by ID
$user->tokens()->where('id', $tokenId)->delete();
Token Abilities (Scopes)
Grant specific permissions to tokens:
// Create token with specific abilities
$token = $user->createToken('mobile-app', [
'post:create',
'post:update',
'post:delete',
'comment:create'
])->plainTextToken;
// Create read-only token
$readOnlyToken = $user->createToken('analytics', [
'post:read',
'user:read'
])->plainTextToken;
Check token abilities in your code:
public function store(Request $request)
{
// Check if token has required ability
if (!$request->user()->tokenCan('post:create')) {
return response()->json([
'success' => false,
'message' => 'Insufficient permissions'
], 403);
}
// Create post...
}
Protecting Routes by Ability
Use the abilities middleware:
Route::middleware(['auth:sanctum', 'abilities:post:create,post:update'])
->post('/posts', [PostController::class, 'store']);
// User must have ALL specified abilities
Route::middleware(['auth:sanctum', 'ability:post:create,post:read'])
->get('/posts', [PostController::class, 'index']);
// User must have AT LEAST ONE of the specified abilities
Token Expiration
Configure token expiration in config/sanctum.php:
'expiration' => 60 * 24, // 24 hours (in minutes)
// Or set to null for tokens that never expire
'expiration' => null,
Check if a token has expired:
$token = $request->user()->currentAccessToken();
if ($token->expires_at && $token->expires_at->isPast()) {
return response()->json([
'success' => false,
'message' => 'Token has expired'
], 401);
}
Customizing Token Model
Add custom fields to track additional token information:
// Migration
Schema::table('personal_access_tokens', function (Blueprint $table) {
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->timestamp('last_used_at')->nullable();
});
// When creating token
$token = $user->createToken($request->device_name);
$token->accessToken->update([
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
return response()->json([
'token' => $token->plainTextToken
]);
SPA Authentication
For SPAs on the same domain, use cookie-based authentication:
// Configure CORS in config/cors.php
'supports_credentials' => true,
// Configure Sanctum stateful domains in config/sanctum.php
'stateful' => explode(',', env(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000'
)),
// Add to .env
SANCTUM_STATEFUL_DOMAINS=localhost:3000,myapp.com
SESSION_DOMAIN=.myapp.com
SPA authentication flow:
// 1. Get CSRF cookie
await axios.get('/sanctum/csrf-cookie');
// 2. Login
await axios.post('/login', {
email: 'user@example.com',
password: 'password'
});
// 3. Make authenticated requests (cookie automatically included)
const response = await axios.get('/api/user');
Registration with Token
Create a registration endpoint that returns a token:
public function register(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|min:8|confirmed',
'device_name' => 'required',
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken($validated['device_name'])->plainTextToken;
return response()->json([
'success' => true,
'message' => 'Registration successful',
'data' => [
'user' => $user,
'token' => $token,
]
], 201);
}
Managing User Tokens
Allow users to view and manage their tokens:
public function tokens(Request $request)
{
$tokens = $request->user()->tokens->map(function ($token) {
return [
'id' => $token->id,
'name' => $token->name,
'abilities' => $token->abilities,
'last_used_at' => $token->last_used_at,
'created_at' => $token->created_at,
'expires_at' => $token->expires_at,
];
});
return response()->json([
'success' => true,
'data' => $tokens
]);
}
public function revokeToken(Request $request, $tokenId)
{
$request->user()
->tokens()
->where('id', $tokenId)
->delete();
return response()->json([
'success' => true,
'message' => 'Token revoked successfully'
]);
}
Testing with Sanctum
Test authenticated endpoints in your tests:
use Laravel\Sanctum\Sanctum;
public function test_user_can_view_posts()
{
$user = User::factory()->create();
// Act as authenticated user
Sanctum::actingAs($user);
$response = $this->getJson('/api/posts');
$response->assertStatus(200);
}
public function test_user_can_create_post_with_ability()
{
$user = User::factory()->create();
// Act as user with specific abilities
Sanctum::actingAs($user, ['post:create']);
$response = $this->postJson('/api/posts', [
'title' => 'Test Post',
'content' => 'Test content',
]);
$response->assertStatus(201);
}
Exercise:
- Install and configure Sanctum in your application
- Create registration and login endpoints that return tokens
- Protect your Post CRUD routes with
auth:sanctum middleware
- Implement token abilities for read-only and full-access tokens
- Create an endpoint that lists all user tokens with their abilities and last used time
- Add a logout endpoint that revokes the current token
Security Best Practices
- Use HTTPS: Always use HTTPS in production to protect tokens in transit
- Short-Lived Tokens: Set reasonable expiration times (24 hours for mobile apps)
- Device Tracking: Store device name, IP, and user agent for security auditing
- Token Rotation: Implement token refresh for long-lived applications
- Ability Scopes: Grant minimal necessary permissions to tokens
- Revocation: Provide easy token revocation for users
- Rate Limiting: Apply rate limiting to authentication endpoints
Summary
Laravel Sanctum provides a simple, secure authentication system perfect for APIs and SPAs. With token abilities, expiration, and easy revocation, it covers most authentication needs without OAuth2 complexity. In the next lesson, we'll explore Laravel Passport for applications requiring full OAuth2 implementation.