Sanctum Security Analyzer
| Analyzer ID | Category | Severity | Time To Fix |
|---|---|---|---|
sanctum-security | 🛡️ Security | High | 10 minutes |
What This Checks
Validates Laravel Sanctum token configuration and security. Checks for:
- Sanctum config file is published (allows customising all Sanctum settings)
- Token creation uses explicit abilities/scopes
- Token abilities are enforced via middleware or
tokenCan()(not just created) - Stateful domains are explicitly listed — no wildcards
HasApiTokenstrait is present on the User model- Token prefix is set for monitoring and log identification
sanctum:prune-expiredis scheduled when token expiration is configured
Token expiration
Token expiration value is intentionally not checked. null (never expire) is the Sanctum default and the correct setting for long-lived CI/CD API keys and service tokens. Expiration is a business decision that depends on how the tokens are used — user-session tokens versus permanent integration keys — which cannot be determined by static analysis.
SPA middleware
EnsureFrontendRequestsAreStateful is not required and its absence is not flagged. SPA cookie-based authentication is an opt-in architectural pattern. Pure bearer-token APIs (CI/CD integrations, mobile backends) correctly omit this middleware; adding it to those apps would boot the session pipeline unnecessarily on every API request.
Why It Matters
- Unrestricted Token Access: Tokens without abilities can access every API endpoint regardless of what the token was issued for
- Decorative Scopes: Token abilities do nothing without enforcement via
abilities:/ability:middleware ortokenCan()— scopes without enforcement are purely cosmetic - Session Hijacking: Wildcard stateful domains allow any origin to make stateful requests
- Missing Trait: Without
HasApiTokens,createToken()andtokenCan()are unavailable on the User model - Token Bloat: Expired tokens accumulate in
personal_access_tokensindefinitely without a pruning schedule, slowing lookups over time
How to Fix
Quick Fix (5 minutes)
Publish the Sanctum config and add HasApiTokens to your User model:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"// app/Models/User.php
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
}Proper Fix (10 minutes)
1. Create tokens with explicit abilities:
// Instead of unrestricted tokens:
// $token = $user->createToken('api-token');
// Use scoped abilities:
$token = $user->createToken('api-token', [
'read:profile',
'update:profile',
'read:orders',
]);2. Enforce abilities on routes:
// routes/api.php
Route::middleware(['auth:sanctum', 'abilities:read,write'])->group(function () {
Route::get('/profile', [ProfileController::class, 'show']);
});Or check inside a controller:
if (! $request->user()->tokenCan('read')) {
abort(403);
}3. Configure stateful domains (no wildcards):
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS',
'app.example.com,localhost:3000'
)),4. Set a token prefix for log identification:
// config/sanctum.php
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', 'myapp_'),5. Schedule expired token pruning (only needed when token expiration is configured):
// app/Console/Kernel.php
$schedule->command('sanctum:prune-expired --hours=24')->daily();// routes/console.php
Schedule::command('sanctum:prune-expired --hours=24')->daily();->withSchedule(function (Schedule $schedule): void {
$schedule->command('sanctum:prune-expired --hours=24')->daily();
})References
Related Analyzers
- Passport Security - Validates Passport OAuth2 configuration
- Session Timeout - Validates session timeout settings
- Auth & Authorization - Validates authentication patterns