laravel/airlock
Laravel Sanctum (formerly Airlock) provides a lightweight authentication system for Laravel SPAs and simple APIs, offering first-party SPA cookie auth plus API token issuing and management for users, mobile apps, and third-party clients.
Installation:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
Configure Middleware:
Add Sanctum’s middleware to your app/Http/Kernel.php:
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
First Use Case:
// Login endpoint
public function login(Request $request) {
$request->validate(['email' => 'required|email', 'password' => 'required']);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response()->json(['message' => 'Unauthorized'], 401);
}
return response()->json([
'token' => $user->createToken('api-token')->plainTextToken,
'user' => $user
]);
}
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
config/sanctum.php: Configure stateful domains, token expiration, and middleware.app/Models/User.php: Ensure the HasApiTokens trait is added.routes/api.php: Define Sanctum-protected routes.$token = $user->createToken('mobile-app-token');
$token->plainTextToken; // Raw token (use once, then revoke)
$user->tokens()->where('name', 'mobile-app-token')->delete();
$token = $user->currentAccessToken();
$token->scopes; // ['read', 'write']
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1')),
X-XSRF-TOKEN header.HasApiTokens:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable {
use HasApiTokens;
public function createCustomToken($name) {
return $this->createToken($name)->tap(function ($token) {
$token->scopes = ['custom-scope'];
});
}
}
Sanctum::getAccessTokenFromRequestUsing(function ($request) {
return $request->bearerToken() ?: $request->cookie('sanctum');
});
'guards' => [
'api' => ['driver' => 'sanctum', 'provider' => 'users'],
'admin-api' => ['driver' => 'sanctum', 'provider' => 'admins'],
],
Route::middleware('auth:admin-api')->get('/admin', function () {
return auth()->user();
});
last_used_at:
'tokens' => [
'prune' => true,
'expire' => true,
'last_used_at' => true, // Track token usage time
],
php artisan sanctum:prune
// Fetch token from Laravel
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const { token } = await response.json();
// Set token and CSRF cookie
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
document.cookie = `XSRF-TOKEN=${response.headers['x-xsrf-token']}; path=/`;
$user = User::factory()->create();
Sanctum::actingAs($user, ['read', 'write']);
$response = $this->actingAs($user)
->withHeaders(['Accept' => 'application/json'])
->post('/api/stateful-endpoint');
personal_access_tokens by default (since v4.2.0). For large-scale apps, ensure your database is optimized:
-- Example for PostgreSQL
CREATE INDEX idx_personal_access_tokens_user_id ON personal_access_tokens(user_id);
401 Unauthorized despite valid token.Authorization: Bearer <token> header.sanctum middleware is applied to the route.// Temporarily disable token validation for debugging
Sanctum::ignoreMissingToken();
419 Page Expired for stateful requests.X-XSRF-TOKEN header with the CSRF cookie value.SameSite cookie attributes are misconfigured (e.g., SameSite=Lax may cause issues in some setups).// In config/sanctum.php
'encrypt_cookies' => false, // Disable if using HTTPS
TokenNotProvidedException or TokenMismatchException.getAccessTokenFromRequestUsing method isn’t overriding default behavior unintentionally.// Custom token retrieval logic
Sanctum::getAccessTokenFromRequestUsing(function ($request) {
return optional($request->user())->currentAccessToken()?->token;
});
403 Forbidden.SANCTUM_STATEFUL_DOMAINS includes all frontend domains (e.g., localhost,myapp.test,api.myapp.com).Origin or Referer header matches a stateful domain.// Allow same-domain requests to be stateful
'stateful' => [
'localhost',
'127.0.0.1',
'myapp.test',
'*.myapp.com',
],
config/sanctum.php for expire and token_ttl settings.last_used_at is enabled if tracking token usage.// Extend token TTL (in minutes)
'tokens' => [
'expire' => true,
'token_ttl' => 1440, // 24 hours
],
PersonalAccessToken model to log events:
class PersonalAccessToken extends \Laravel\Sanctum\PersonalAccessToken {
protected static function booted() {
static::created(function ($token) {
Log::info('Token created', ['token' => $token->token]);
});
static::deleted(function ($token) {
Log::info
How can I help you explore Laravel packages today?