Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
••
IT Security NachrichtenStop Talking About A.I. Like a Human(01.10.2026 um 11:01 Uhr)
•
IT Security NachrichtenQueensland Cybersecurity Department Loses $809,000 in Cyberattack(01.10.2026 um 11:00 Uhr)
•
IT Security NachrichtenEurope’s Police Chiefs Face Growing Threat of AI-Driven Crime(01.10.2026 um 11:01 Uhr)
••
IT Security NachrichtenWhy AI agents are like the dog that pushed kids into the Seine(01.10.2026 um 11:00 Uhr)
••••••
IT Security NachrichtenStop Talking About A.I. Like a Human(01.10.2026 um 11:01 Uhr)
•
IT Security NachrichtenQueensland Cybersecurity Department Loses $809,000 in Cyberattack(01.10.2026 um 11:00 Uhr)
•
IT Security NachrichtenEurope’s Police Chiefs Face Growing Threat of AI-Driven Crime(01.10.2026 um 11:01 Uhr)
••
IT Security NachrichtenWhy AI agents are like the dog that pushed kids into the Seine(01.10.2026 um 11:00 Uhr)
••••
Intelligence View
⚡ tsecurity.de Intelligence

Laravel Sanctum API Authentication: The Complete Production Guide

There's a quiet assumption baked into almost every Laravel AI integration tutorial: authentication exists. Routes are protected. Tokens are issued. The API is…

Beitrag
0
Seite
0
↗ Quelle (dev.to)
Social ReaktionenReagiere als Erste:r — dein Feedback zählt!

There's a quiet assumption baked into almost every Laravel AI integration tutorial: authentication exists. Routes are protected. Tokens are issued. The API is locked down.



That assumption breaks the moment you sit down to build something real.



Laravel Sanctum is the framework's answer to lightweight API token authentication. It ships with Laravel, it integrates cleanly with Eloquent, and it handles the two most common authentication patterns - SPA cookie-based sessions and mobile/external API token issuance, without pulling in a full OAuth server. This guide covers both patterns, but it leans hard into the personal access token model, because that's what you need when you're building an API that your own frontend, mobile app, or third-party client will consume.



By the end, you'll have a production-ready authentication layer: token issuance with ability scoping, protected routes, revocation endpoints, rate limiting via Redis, and a multi-tenant token pattern that holds up under real load. We're also covering the Laravel 11/12 bootstrap/app.php configuration style throughout, no legacy Kernel.php references.



Let's get into it.






What Sanctum Actually Does



Before writing a single line of code, you need to understand where Sanctum fits in the ecosystem. Sanctum is not OAuth. It doesn't issue refresh tokens. It doesn't support third-party authorization flows. If you need those things, reach for Laravel Passport.



What Sanctum does exceptionally well is issue hashed personal access tokens tied to your users table via a polymorphic personal_access_tokens table. Each token can carry a set of abilities - scoped permissions that your application checks at runtime. The token itself is a random string; only the SHA-256 hash lives in the database. That's a sensible default.



For SPAs on the same domain, Sanctum piggybacks on Laravel's existing session authentication via a cookie. This is the EnsureFrontendRequestsAreStateful middleware doing its job. We'll touch on this briefly, but the majority of this guide focuses on token-based auth for API clients.






Installation and Setup (Laravel 11 / 12)



Sanctum ships with Laravel 11 and 12. If you're starting a fresh project, it's already in your composer.json. Confirm it's there:




composer show laravel/sanctum






If you're on an older install that upgraded to Laravel 11, you may need to install it:




composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate






That publish step drops a config/sanctum.php file and a migration for the personal_access_tokens table. Run the migration before anything else.






bootstrap/app.php - The Laravel 11/12 Way



In Laravel 11, middleware registration moved out of Kernel.php and into bootstrap/app.php. This is where you register the Sanctum stateful middleware for SPA authentication:




// bootstrap/app.php
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

->withMiddleware(function (Middleware $middleware) {
$middleware->statefulApi();
})






Calling $middleware->statefulApi() is the clean Laravel 11 way to register Sanctum's SPA middleware. Avoid manually listing the middleware class inline, statefulApi() handles the correct ordering.



For token-only APIs (no SPA), you can skip this entirely. Your token-protected routes will use the auth:sanctum guard, which handles everything through the Authorization: Bearer header.






Configuring the Guard



In config/auth.php, make sure your api guard points to Sanctum:




'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api' => [
'driver' => 'sanctum',
'provider' => 'users',
],
],






This means auth:api and auth:sanctum both resolve to the same thing. Pick one and be consistent across your routes.






Preparing the User Model



Add the HasApiTokens trait to your User model:




use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}






That trait gives Eloquent's User model the createToken(), tokens(), and currentAccessToken() methods. Everything downstream depends on this being in place.






Issuing Personal Access Tokens



Token issuance happens through a dedicated endpoint. Keep it clean: one controller, one responsibility.




// app/Http/Controllers/Auth/TokenController.php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use App\Models\User;

class TokenController extends Controller
{
public function issue(Request $request): \Illuminate\Http\JsonResponse
{
$request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
'device_name' => ['required', 'string', 'max:255'],
]);

$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(
$request->device_name,
['api:read', 'api:write']
);

return response()->json([
'token' => $token->plainTextToken,
]);
}

public function revoke(Request $request): \Illuminate\Http\JsonResponse
{
$request->user()->currentAccessToken()->delete();

return response()->json(['message' => 'Token revoked.']);
}
}






The device_name field is part of Sanctum's design, it lets users see which device or client issued each token. The ValidationException approach returns a clean 422 with field-level errors rather than a 401, which is what most frontend clients expect.



Route registration:




// routes/api.php
use App\Http\Controllers\Auth\TokenController;

Route::post('/auth/token', [TokenController::class, 'issue']);
Route::delete('/auth/token', [TokenController::class, 'revoke'])->middleware('auth:sanctum');







Architect's Note: Never issue tokens from inside a route closure. The moment you need to add logging, rate limiting, or ability scoping, you're refactoring. Always use a dedicated controller from day one, the Service Container will thank you when you need to inject dependencies later.







Token Abilities: Scoped Permissions



Token abilities are Sanctum's lightweight alternative to OAuth scopes. You define them as strings at issuance time, then check them at the route or controller level.




// Read-only token
$token = $user->createToken('mobile-client', ['api:read']);

// Full-access token
$token = $user->createToken('admin-panel', ['api:read', 'api:write', 'api:delete']);

// AI feature token
$token = $user->createToken('ai-assistant', ['ai:query', 'api:read']);






Check abilities in your controllers using tokenCan():




public function store(Request $request): JsonResponse
{
if (! $request->user()->tokenCan('api:write')) {
return response()->json(['error' => 'Insufficient token abilities.'], 403);
}

// proceed with write operation
}






Or apply the abilities middleware directly on route groups:




Route::middleware(['auth:sanctum', 'abilities:api:read,api:write'])
->group(function () {
Route::post('/documents', [DocumentController::class, 'store']);
});

Route::middleware(['auth:sanctum', 'ability:api:read'])
->group(function () {
Route::get('/documents', [DocumentController::class, 'index']);
});






Note the difference: abilities (plural) requires the token to have all listed abilities. ability (singular) requires at least one. This distinction catches people off guard in production.






Protecting Routes



Standard protected route group for a JSON API:




// routes/api.php

Route::middleware('auth:sanctum')->group(function () {

Route::apiResource('documents', DocumentController::class);

Route::prefix('ai')->group(function () {
Route::post('/query', [AiQueryController::class, 'query'])
->middleware('ability:ai:query');
Route::get('/history', [AiQueryController::class, 'history'])
->middleware('ability:api:read');
});

});






The auth:sanctum guard resolves the authenticated user from the Bearer token, making $request->user() available throughout the request lifecycle.






Token Revocation



You need at minimum three revocation endpoints:




// Revoke current token (logout this device)
Route::delete('/auth/token', [TokenController::class, 'revoke'])
->middleware('auth:sanctum');

// Revoke a specific token by ID
Route::delete('/auth/tokens/{tokenId}', [TokenController::class, 'revokeById'])
->middleware('auth:sanctum');

// Revoke all tokens (logout everywhere)
Route::delete('/auth/tokens', [TokenController::class, 'revokeAll'])
->middleware('auth:sanctum');






The controller methods:




public function revokeById(Request $request, int $tokenId): JsonResponse
{
$deleted = $request->user()
->tokens()
->where('id', $tokenId)
->delete();

if (! $deleted) {
return response()->json(['error' => 'Token not found.'], 404);
}

return response()->json(['message' => 'Token revoked.']);
}

public function revokeAll(Request $request): JsonResponse
{
$request->user()->tokens()->delete();

return response()->json(['message' => 'All tokens revoked.']);
}






The ->where('id', $tokenId) scoping on the tokens relationship is critical. Without it, a user could delete another user's token by guessing IDs — an IDOR vulnerability. The relationship scope ensures only that user's tokens are in play before the delete fires.




Production Pitfall: Token revocation is only as fast as your database query. Under heavy concurrent traffic, a "revoke all" operation that hits an unindexed tokenable_id column will cause measurable latency spikes. Add an index to personal_access_tokens.tokenable_id, Laravel's migration doesn't include it by default in all versions.





// In a new migration
Schema::table('personal_access_tokens', function (Blueprint $table) {
$table->index(['tokenable_type', 'tokenable_id']);
});









Token Expiry



By default, Sanctum tokens don't expire. Set an expiry in config/sanctum.php:




'expiration' => 60 * 24 * 30, // 30 days, in minutes






Pair this with a scheduled prune command:




// routes/console.php (Laravel 11)
Schedule::command('sanctum:prune-expired --hours=24')->daily();






Left unchecked, the personal_access_tokens table grows indefinitely and starts dragging down every authenticated request.






Rate Limiting with Redis



The auth:sanctum middleware doesn't include rate limiting. You're responsible for adding it. This is not optional for any API that calls an external AI provider - without it, a single misbehaving client can burn through your budget in minutes.



Define rate limiters in bootstrap/app.php:




use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('api', function (Request $request) {
return $request->user()
? Limit::perMinute(60)->by($request->user()->id)
: Limit::perMinute(10)->by($request->ip());
});

RateLimiter::for('ai', function (Request $request) {
return $request->user()
? Limit::perMinute(10)->by($request->user()->id)
: Limit::perMinute(2)->by($request->ip());
});






Apply them to route groups:




Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
// standard API routes
});

Route::middleware(['auth:sanctum', 'throttle:ai'])->group(function () {
// AI query routes
});






Switch your cache driver to Redis. The default file driver won't share rate limit counts across multiple server instances.




CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379







Efficiency Gain: Redis handles both rate limiting and Sanctum's token lookup cache on the same connection. At scale, Sanctum resolves the user on every request by hashing the incoming token and hitting the database. If you're seeing measurable auth overhead in production profiling, an application-level cache keyed to the token hash eliminates that DB hit - but profile first, optimize second.







Multi-Tenant Token Scoping



A user who belongs to multiple organizations shouldn't be able to use a token issued for Organization A to access Organization B's data. The cleanest fix: store a team_id on the token itself using a custom token model.



Extend the default token model:




// app/Models/PersonalAccessToken.php

namespace App\Models;

use Laravel\Sanctum\PersonalAccessToken as SanctumToken;

class PersonalAccessToken extends SanctumToken
{
protected $fillable = [
'name',
'token',
'abilities',
'expires_at',
'team_id',
];
}






Register it in AppServiceProvider:




use Laravel\Sanctum\Sanctum;
use App\Models\PersonalAccessToken;

public function boot(): void
{
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}






Add the column:




Schema::table('personal_access_tokens', function (Blueprint $table) {
$table->unsignedBigInteger('team_id')->nullable()->index();
});






Issue tokens with team context:




$token = $user->createToken($request->device_name, ['api:read', 'api:write']);
$token->accessToken->forceFill(['team_id' => $currentTeam->id])->save();






Authorize against both user and team on every request:




$tokenTeamId = $request->user()->currentAccessToken()->team_id;

if ($tokenTeamId !== $resource->team_id) {
abort(403, 'Token not authorized for this team.');
}









Listing Tokens for the Authenticated User



Give users visibility into their active tokens:




public function index(Request $request): JsonResponse
{
$tokens = $request->user()->tokens()
->select(['id', 'name', 'abilities', 'last_used_at', 'expires_at', 'created_at'])
->latest()
->get()
->map(function ($token) {
return [
'id' => $token->id,
'name' => $token->name,
'abilities' => $token->abilities,
'last_used_at' => $token->last_used_at?->toDateTimeString(),
'expires_at' => $token->expires_at?->toDateTimeString(),
'created_at' => $token->created_at->toDateTimeString(),
];
});

return response()->json(['tokens' => $tokens]);
}






The raw token value is never returned here, that's only available at issuance via plainTextToken. After that, only the hash is stored.






Testing Sanctum Authentication



Sanctum ships with a clean testing helper:




use Laravel\Sanctum\Sanctum;

class DocumentApiTest extends TestCase
{
use RefreshDatabase;

public function test_authenticated_user_can_list_documents(): void
{
$user = User::factory()->create();
Sanctum::actingAs($user, ['api:read']);

$this->getJson('/api/documents')->assertStatus(200);
}

public function test_token_without_write_ability_cannot_create_document(): void
{
$user = User::factory()->create();
Sanctum::actingAs($user, ['api:read']); // no api:write

$this->postJson('/api/documents', [
'title' => 'Test',
'content' => 'Test content',
])->assertStatus(403);
}

public function test_unauthenticated_request_is_rejected(): void
{
$this->getJson('/api/documents')->assertStatus(401);
}
}






Sanctum::actingAs() bypasses the actual token lookup and tells the guard to treat the request as authenticated with those abilities. Your tests stay fast; no real tokens are issued.




Word to the Wise: Test the ability checks explicitly. Developers regularly ship applications where the happy path tests pass but the authorization boundaries are never verified. A token with api:read silently passing a api:write endpoint is a data integrity problem, not just a security one. Write the negative cases, they catch the middleware misconfiguration you didn't know you'd made.







Returning Consistent Auth Error Responses



By default, an unauthenticated request to a Sanctum-protected route returns a redirect to the login page. That's wrong for a JSON API. Fix it in bootstrap/app.php:




->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (AuthenticationException $e, Request $request) {
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
});

$exceptions->render(function (AuthorizationException $e, Request $request) {
if ($request->expectsJson()) {
return response()->json(['error' => 'Forbidden.'], 403);
}
});
})






A 302 redirect from an API client that expected a 401 is a confusing failure mode that causes hours of frontend debugging.





For a Vue or React SPA on the same top-level domain, Sanctum's stateful middleware handles auth through the session cookie — no tokens needed.



The flow: the SPA GETs /sanctum/csrf-cookie to initialize the CSRF cookie, POSTs credentials to your login endpoint, then includes the X-XSRF-TOKEN header on subsequent requests.



Configure your stateful domains in config/sanctum.php:




'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),






Set SANCTUM_STATEFUL_DOMAINS explicitly in production. Wildcard domains are not supported.




Edge Case Alert: If your SPA and API are on different subdomains (app.example.com and api.example.com), the session cookie won't work across domains due to browser SameSite restrictions. Use token-based auth even for your SPA in this case. The cookie approach only works when both are served from the same domain.







Production Deployment Checklist



Database





  • personal_access_tokens migration applied

  • Index on tokenable_type and tokenable_id exists

  • Token expiry configured and sanctum:prune-expired scheduled



Configuration





  • APP_KEY is set and rotated from the default


  • SESSION_DRIVER is redis or database in multi-server setups


  • CACHE_DRIVER is redis for rate limiting across instances


  • SANCTUM_STATEFUL_DOMAINS explicitly set if using SPA auth



Rate Limiting




  • Named rate limiters defined for all API route groups

  • Stricter limiters applied to AI query endpoints

  • Rate limit responses return Retry-After headers



Security





  • Authorization header stripped at the CDN for non-API routes

  • HTTPS enforced — tokens over HTTP is a non-starter

  • Tokens are never logged — audit your logging configuration



Monitoring




  • Track personal_access_tokens table row count as a metric

  • Alert on unusual token issuance spikes

  • Log token revocation events for audit trails






What Sanctum Doesn't Cover



Sanctum doesn't do: OAuth2 authorization flows, refresh tokens, third-party client authorization, dynamic scope negotiation, or JWT issuance. If any of those are requirements, use Passport.



For the overwhelming majority of Laravel APIs — including every AI integration pattern — Sanctum's personal access tokens, SPA sessions, ability scoping, and revocation model cover the full authentication lifecycle. Don't reach for Passport's complexity if Sanctum's model fits your use case. The operational overhead isn't trivial.






Official Documentation





Originally published at origin-main.com — Laravel + AI integration guides for production developers.

2. Cyber Threat Intelligence & Forensik

CTI Threat Relationship Graph3 Knoten / 2 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Laravel Sanctum API Authentication: The Complete Production Guide

Thematisch verwandte Begriffe: Laravel, Sanctum, Authentication, Complete · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag