Laravel 13 y Sanctum: protege tu API REST con tokens de acceso
Con php artisan install:api tienes Sanctum y la migración de tokens listos en un minuto; con un trait y un middleware, tu API REST en Laravel 13 queda protegida por Bearer tokens. Así se hace, paso a paso.
Qué es Sanctum y cuándo usarlo en Laravel 13
Sanctum es el paquete first-party de Laravel para autenticación ligera. Nació para resolver dos problemas distintos con una sola instalación: emitir tokens de acceso para consumidores externos (apps móviles, servicios de terceros, frontends separados) y permitir que tu propia SPA se autentique con cookies, como una sesión clásica. En Laravel 13 se instala como paquete independiente, igual que en la serie 12, y su documentación oficial 13.x lo describe como la vía recomendada para APIs propias.
Tokens de API frente a autenticación SPA
La diferencia está en quién consume la API y desde dónde. Un token de acceso es una cadena opaca que el cliente envía en cada petición; es ideal para apps móviles y para terceros que no gestionan cookies contigo. La autenticación SPA, en cambio, usa cookies de sesión con protección CSRF y está pensada para tu propio frontend, servido desde el mismo dominio o un subdominio. Elegir mal entre una y otra es la causa de buena parte de los problemas de CORS que se ven en producción.
Sanctum vs Passport en 2026: cuándo necesitas OAuth
Passport implementa OAuth2 completo: clientes de terceros, refresh tokens y flujos de autorización pensados para que otras aplicaciones accedan a datos de tus usuarios con su consentimiento explícito. Sanctum es más ligero y cubre la mayoría de los casos: una API que tú controlas y cuyos consumidores conoces. La regla práctica en 2026 sigue siendo la misma: si necesitas que terceros se registren como clientes OAuth, Passport; si solo quieres proteger tu API, Sanctum.
Instalación: php artisan install:api
En Laravel 12 y 13 las rutas de API ya no vienen por defecto en instalaciones nuevas. El comando php artisan install:api las habilita en un solo paso: instala el paquete Sanctum, crea el archivo routes/api.php y publica la migración que crea la tabla personal_access_tokens. Después de ejecutarlo solo falta correr las migraciones.
php artisan install:api
php artisan migrateQué crea el comando: routes/api.php y la migración personal_access_tokens
El archivo routes/api.php llega con una ruta de ejemplo y el prefijo /api ya aplicado. La migración personal_access_tokens guarda cada token con su nombre, sus abilities (en JSON) y los campos de caducidad y último uso; Laravel solo almacena el hash SHA-256 del token, nunca el valor en claro, así que si alguien roba la base de datos no puede reutilizar los tokens.
Tu primer token: el modelo User y el trait HasApiTokens
Para que un usuario pueda emitir tokens, el modelo que lo representa debe usar el trait HasApiTokens. Es el único cambio obligatorio en el modelo: el trait añade la relación con los tokens y los métodos para crearlos y revocarlos.
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
}createToken() y el plainTextToken que solo ves una vez
Crear un token es una línea. El método createToken() devuelve un objeto cuyo atributo plainTextToken contiene la cadena real, y ese valor solo se muestra en esta respuesta: Laravel guarda únicamente su hash. Por eso el flujo típico de login de una app móvil es devolver ese token al cliente y pedirle que lo guarde de forma segura (por ejemplo, en el llavero del sistema).
$token = $user->createToken('app-movil')->plainTextToken;
return response()->json(['token' => $token]);Proteger rutas con auth:sanctum
Una vez que el usuario tiene su token, proteger rutas es cuestión de añadir el guard sanctum al middleware auth. Lo habitual es agrupar las rutas privadas y aplicar el middleware al grupo completo.
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', fn (Request $request) => $request->user());
Route::get('/orders', [OrderController::class, 'index']);
});Enviar el Bearer token desde el cliente
El cliente envía el token en la cabecera Authorization con el prefijo Bearer. Desde la línea de comandos se prueba así:
curl -H "Authorization: Bearer TU_TOKEN" \
https://tu-dominio.com/api/userSi el token es válido, $request->user() devuelve el usuario autenticado; si falta o es inválido, la respuesta es un 401. La app móvil debe añadir la misma cabecera a cada petición, normalmente desde un interceptor del cliente HTTP.
Abilities: permisos por token
Un token puede llevar abilities, que son permisos opcionales que limitan lo que ese token concreto puede hacer. Es la forma de dar a cada dispositivo o integración solo el acceso mínimo que necesita. En el ejemplo de la tienda, la app de reparto podría tener un token con la ability orders:read y sin acceso a los datos de pago.
$token = $user->createToken('app-reparto', ['orders:read'])->plainTextToken;Comprobar permisos con tokenCan()
Dentro de un controlador puedes preguntar al usuario autenticado si el token con el que llegó tiene una ability concreta. tokenCan() mira las abilities del token actual, no las del usuario en general, lo que permite rutas compartidas con comportamientos distintos según el cliente.
if ($request->user()->tokenCan('orders:read')) {
// devolver pedidos
}Caducidad de tokens con expires_at
Puedes hacer que un token caduque automáticamente pasando una fecha de expiración a createToken(); el guard lo rechazará después de esa fecha. También puedes revocar tokens en cualquier momento, por ejemplo en el logout de la app móvil, borrando los tokens del usuario.
$token = $user->createToken('app-movil', ['*'], now()->addDays(30))->plainTextToken;
// logout: revocar todos los tokens del usuario
$user->tokens()->delete();Autenticación SPA: cookies, CSRF y CORS
Si el consumidor es tu propia SPA (Vue, React o Livewire en el mismo dominio), Sanctum ofrece el modo SPA: en lugar de tokens, el login devuelve una cookie de sesión protegida contra CSRF, y el guard valida la sesión como en una app clásica. Para activarlo hay que configurar el dominio de la SPA en config/sanctum.php, usar el middleware de estado y permitir credenciales en CORS.
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1')),Cuándo NO usar tokens de API: tu propia SPA de primera parte
La documentación oficial recomienda el modo SPA para tu propio frontend: las cookies de sesión son más seguras que guardar un token en localStorage (menos superficie de robo por XSS) y no tienes que gestionar caducidades manualmente. Los tokens quedan para lo que son: apps móviles, integraciones de terceros y cualquier cliente que no pueda mantener una cookie de sesión contigo.
Errores típicos que rompen la primera petición
El más común es olvidar la cabecera Authorization o escribir el prefijo mal (Bearer con mayúscula y un espacio). Le sigue no haber ejecutado php artisan migrate, con lo que la tabla de tokens no existe y Sanctum devuelve un error de base de datos. En SPAs, el fallo clásico es el CORS mal configurado: falta supports_credentials o el dominio de la SPA no está en stateful. Y un detalle que cuesta horas: si regeneras el token en cada login, el cliente antiguo se queda sin acceso; guarda el token en el cliente y reutilízalo hasta que caduque o se revoque.
Conclusión
Proteger una API REST en Laravel 13 con Sanctum se reduce a cuatro piezas: el comando de instalación, el trait en el modelo, el middleware en las rutas y la cabecera Bearer en el cliente. Con abilities y caducidad controlas qué puede hacer cada token y durante cuánto tiempo, y con el modo SPA cubres también tu propio frontend sin salir del mismo paquete. Si estás montando tu primera API en Laravel 13, este es el punto de partida; el siguiente paso natural es explorar el eager loading para que tus endpoints no sufran el problema N+1.