Laravel cookbook · Intermediate
Issue scoped API tokens with Sanctum
Integrations need long-lived API access with limited permissions that can be revoked.
Approach
Let users create personal access tokens with explicit abilities and an expiry, protect routes with
auth:sanctum plus ability middleware, and show the plain token only once.
Solution
<?php
// app/Http/Controllers/ApiTokenController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ApiTokenController extends Controller
{
public function store(Request $request)
{
$data = $request->validate([
'name' => ['required', 'string', 'max:50'],
'abilities' => ['required', 'array'],
'abilities.*' => ['in:orders:read,orders:write,reports:read'],
]);
$token = $request->user()->createToken($data['name'], $data['abilities'], now()->addDays(90));
return response()->json(['token' => $token->plainTextToken], 201); // shown once
}
public function destroy(Request $request, int $tokenId)
{
$request->user()->tokens()->whereKey($tokenId)->delete();
return response()->noContent();
}
}
<?php
// routes/api.php
use App\Http\Controllers\OrderController;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->group(function () {
Route::get('/orders', [OrderController::class, 'index'])->middleware('ability:orders:read');
Route::post('/orders', [OrderController::class, 'store'])->middleware('ability:orders:write');
});
Pitfalls
- Tokens with
['*']abilities for every integration. - No expiry, so leaked tokens work forever; also prune expired tokens (
sanctum:prune-expired). - Logging the plain token or storing it unhashed elsewhere.
Variations
- For browser SPAs on the same site, use Sanctum's cookie-based session authentication instead of tokens.
- For third-party apps acting on behalf of users, use OAuth (Laravel Passport).
Further reading: Sanctum API tokens
Reference: https://laravel.com/docs/13.x/sanctum#api-token-authentication
More Laravel recipes
- Authorize actions with policies
- Cache expensive data with stale-while-revalidate
- Call external APIs with timeouts, retries and fakes
- Catch N+1 queries with strict Eloquent mode
- Delete old records automatically with prunable models
- Dispatch events and listeners after the transaction commits
- Make queued jobs reliable with retries, backoff and uniqueness
- Prevent race conditions with transactions and row locks