Laravel Sanctum
Com utilitzar Laravel Sanctum per autenticar SPAs, aplicacions mòbils i APIs amb tokens lleugeros.
Què és Sanctum?#
Laravel Sanctum proporciona un sistema d'autenticació lleuger per a tres escenaris: APIs amb tokens, SPAs (Single Page Applications) i aplicacions mòbils. A diferència de Passport, que implementa OAuth2 complet amb tota la seva complexitat, Sanctum ofereix una solució senzilla i pràctica que cobreix la majoria de necessitats.
Per a APIs, Sanctum permet als usuaris crear tokens d'API amb permisos específics (abilities). Per a SPAs que viuen al mateix domini que l'aplicació Laravel, Sanctum utilitza autenticació basada en cookies de sessió, aprofitant el sistema d'autenticació que ja té Laravel. Això el fa ideal per a frontends fets amb React, Vue o Angular que consumeixen una API Laravel.
Instal·lació#
A partir de Laravel 11, Sanctum s'instal·la amb una sola comanda:
php artisan install:apiAquesta comanda publica la migració de la taula personal_access_tokens, afegeix el fitxer api.php de rutes i configura el middleware de Sanctum. Després, executa les migracions:
php artisan migrateEl model User ha d'utilitzar el trait HasApiTokens per poder crear i gestionar tokens:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}API Tokens#
Crear tokens#
El cas d'ús més directe de Sanctum és crear tokens d'API perquè els usuaris puguin autenticar les seves peticions. El mètode createToken retorna una instància de NewAccessToken que conté el token en text pla:
use App\Models\User;
$user = User::find(1);
// Crear un token sense restriccions
$token = $user->createToken('token-general');
// El token en text pla (només disponible en el moment de creació)
$plainTextToken = $token->plainTextToken;El token en text pla només està disponible immediatament després de crear-lo. Sanctum emmagatzema un hash SHA-256 del token a la base de dades, així que no és possible recuperar el valor original més endavant. Per això, cal retornar-lo a l'usuari en la resposta de creació.
Un patró habitual és crear el token quan l'usuari fa login a través de l'API:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
Route::post('/login', function (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)) {
return response()->json([
'message' => 'Les credencials no són vàlides.',
], 401);
}
$token = $user->createToken($request->device_name);
return response()->json([
'token' => $token->plainTextToken,
'user' => $user,
]);
});L'usuari envia el token a cada petició a través de la capçalera Authorization:
Authorization: Bearer 1|abc123def456...
Abilities (permisos)#
Cada token pot tenir un conjunt de permisos que limiten quines accions pot realitzar. Això és útil quan vols que un token només pugui llegir dades però no modificar-les, o quan vols donar permisos granulars a integracions de tercers:
// Token amb permisos específics
$token = $user->createToken('token-lectura', ['articles:read', 'comments:read']);
// Token amb tots els permisos
$token = $user->createToken('token-admin', ['*']);
// Token per a una integració que només pot crear articles
$token = $user->createToken('cms-integration', [
'articles:create',
'articles:update',
'media:upload',
]);Per comprovar si el token actual té un permís determinat, utilitza tokenCan o tokenCant:
Route::post('/articles', function (Request $request) {
if ($request->user()->tokenCan('articles:create')) {
// Crear l'article
}
// O de manera inversa
if ($request->user()->tokenCant('articles:create')) {
abort(403, 'El teu token no té permís per crear articles.');
}
});Sanctum també inclou dos middlewares per protegir rutes basant-se en abilities:
// L'usuari ha de tenir TOTES les abilities indicades
Route::post('/articles', [ArticleController::class, 'store'])
->middleware(['auth:sanctum', 'ability:articles:create']);
// L'usuari ha de tenir ALMENYS UNA de les abilities
Route::get('/articles', [ArticleController::class, 'index'])
->middleware(['auth:sanctum', 'ability:articles:read,articles:admin']);Revocar tokens#
Pots revocar tokens de diverses maneres segons la necessitat:
// Revocar el token que s'ha utilitzat en la petició actual
$request->user()->currentAccessToken()->delete();
// Revocar un token específic per ID
$request->user()->tokens()->where('id', $tokenId)->delete();
// Revocar tots els tokens de l'usuari (logout de tots els dispositius)
$request->user()->tokens()->delete();
// Revocar tots els tokens excepte l'actual
$request->user()->tokens()
->where('id', '!=', $request->user()->currentAccessToken()->id)
->delete();Un endpoint típic de logout revocaria el token actual:
Route::post('/logout', function (Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Sessió tancada.']);
})->middleware('auth:sanctum');Expiració de tokens#
Per defecte, els tokens de Sanctum no expiren mai. Pots configurar una expiració al fitxer config/sanctum.php:
// config/sanctum.php
'expiration' => 60 * 24, // 24 hores en minutsQuan configures una expiració, els tokens caducats seguiran existint a la base de dades. Pots netejar-los amb una comanda programada:
// routes/console.php
use Laravel\Sanctum\PersonalAccessToken;
Schedule::command('sanctum:prune-expired --hours=24')->daily();Protegir rutes API#
Per protegir rutes d'API amb Sanctum, aplica el middleware auth:sanctum. Les peticions hauran d'incloure un token vàlid a la capçalera Authorization:
use Illuminate\Http\Request;
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::apiResource('articles', ArticleController::class);
Route::apiResource('comments', CommentController::class);
});Dins de les rutes protegides, pots accedir a l'usuari autenticat amb $request->user() com amb qualsevol altre guard d'autenticació.
Autenticació SPA#
L'autenticació SPA és el segon mode de funcionament de Sanctum. Quan la teva SPA (React, Vue, Angular, etc.) viu al mateix domini que l'aplicació Laravel (o en un subdomini), Sanctum pot utilitzar autenticació basada en cookies de sessió en lloc de tokens. Això és més segur perquè les cookies de sessió no s'emmagatzemen al JavaScript del client.
Configuració del backend#
Primer, configura els dominis que es consideren "stateful" (que poden utilitzar cookies de sessió):
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,localhost:5173,127.0.0.1,127.0.0.1:8000'
)),O al fitxer .env:
SANCTUM_STATEFUL_DOMAINS=localhost:3000,localhost:5173Configura CORS perquè el frontend pugui enviar cookies:
// config/cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout'],
'supports_credentials' => true,El fitxer .env ha de tenir el domini de sessió correcte:
SESSION_DOMAIN=localhostPer a producció amb subdominis (per exemple, api.example.com i app.example.com), utilitza un punt davant del domini:
SESSION_DOMAIN=.example.com
SANCTUM_STATEFUL_DOMAINS=app.example.comFlux d'autenticació al frontend#
El procés d'autenticació SPA té dos passos. Primer, el frontend sol·licita un cookie CSRF. Després, fa la petició de login normal:
// 1. Obtenir el cookie CSRF
await fetch('http://localhost:8000/sanctum/csrf-cookie', {
credentials: 'include',
});
// 2. Login amb credencials
const response = await fetch('http://localhost:8000/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
email: 'usuari@example.com',
password: 'secret',
}),
});Un cop autenticat, totes les peticions posteriors inclouran automàticament el cookie de sessió:
// Les peticions posteriors ja estan autenticades
const response = await fetch('http://localhost:8000/api/user', {
headers: {
'Accept': 'application/json',
},
credentials: 'include',
});
const user = await response.json();La clau és credentials: 'include' a totes les peticions, que indica al navegador que inclogui les cookies. Si utilitzes Axios, pots configurar-ho globalment:
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'http://localhost:8000';
// Obtenir CSRF cookie
await axios.get('/sanctum/csrf-cookie');
// Login
await axios.post('/login', { email, password });
// Peticions autenticades
const { data } = await axios.get('/api/user');Protecció CSRF#
Quan el frontend fa peticions POST, PUT, PATCH o DELETE, Sanctum espera rebre el token CSRF. Normalment, el cookie XSRF-TOKEN que Laravel estableix s'envia automàticament pel navegador. Axios, per exemple, llegeix aquest cookie i l'afegeix com a capçalera X-XSRF-TOKEN de manera automàtica.
Si el teu framework HTTP no ho fa automàticament, cal extreure el valor del cookie i enviar-lo manualment:
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
return decodeURIComponent(parts.pop().split(';').shift());
}
}
await fetch('/api/articles', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
},
credentials: 'include',
body: JSON.stringify({ title: 'Nou article' }),
});Autenticació per a aplicacions mòbils#
Per a aplicacions mòbils (iOS, Android, Flutter, React Native), l'autenticació basada en cookies no és pràctica. En aquest cas, Sanctum utilitza tokens d'API. L'aplicació mòbil fa login amb les credencials de l'usuari, rep un token i l'utilitza per a totes les peticions posteriors:
// Endpoint de login per a l'app mòbil
Route::post('/mobile/login', function (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' => ['Les credencials proporcionades no són correctes.'],
]);
}
// Revocar tokens anteriors del mateix dispositiu
$user->tokens()->where('name', $request->device_name)->delete();
return response()->json([
'user' => $user,
'token' => $user->createToken($request->device_name)->plainTextToken,
]);
});L'aplicació mòbil emmagatzema el token de manera segura (per exemple, al Keychain d'iOS o al EncryptedSharedPreferences d'Android) i l'inclou a totes les peticions:
GET /api/user HTTP/1.1
Authorization: Bearer 1|abc123def456...
Accept: application/json
Sanctum vs Passport#
Sanctum és la opció recomanada per a la majoria d'aplicacions. És més senzill d'instal·lar, configurar i utilitzar. Utilitza Sanctum quan necessitis tokens d'API simples amb abilities, autenticació per a SPAs al mateix domini, o autenticació per a aplicacions mòbils.
Passport és necessari quan l'aplicació ha de funcionar com a proveïdor OAuth2: per exemple, si vols que tercers puguin crear aplicacions que accedeixin a les dades dels teus usuaris amb el flux d'autorització OAuth2 (com fa GitHub o Google). Si no necessites OAuth2, Sanctum és suficient.