Índice de contenido
- ¿Qué es Laravel Sanctum?
- 1 Autenticación vía SPA
- 2 En base a API Tokens
- Generar tokens de acceso
- Más ejemplos con Sanctum
- Estrategias para Renovar el Token de Acceso en una Rest API con Laravel Sanctum
- 1. Autenticación Híbrida: Cookies frente a Tokens
- 2. El Ciclo de Vida del Token: Mitigación de Registros Muertos
- Estrategia Cero: Depuración Programada (Cron Jobs)
- Estrategia Uno: Renovación Activa (Silent Refresh)
- Estrategia Dos: Extensión Dinámica del Token
- 3. Implementación Práctica: Validación Temprana y Extensión de Sesión
- Justificación de la Verificación de Instancia
- 4. Centralización e Identificación Única de Dispositivos
- Limitaciones del Control por Dispositivo
Laravel Sanctum es el paquete oficial de autenticación para aplicaciones de página única (SPA) y aplicaciones móviles dentro del ecosistema Laravel. Su propósito principal es proteger una Rest API en Laravel mediante autenticación requerida, sin la complejidad de implementar un servidor OAuth completo. A diferencia de agregar una sesión tradicional, las APIs REST se recomiendan como sin estado (stateless), y es exactamente ahí donde laravel/sanctum entra al juego: permite que cada usuario de tu aplicación genere múltiples tokens de API para su cuenta, ofreciendo un sistema de autenticación ligero tanto para SPAs como para APIs simples.
Laravel Sanctum es una herramienta ideal cuando quieres proteger una Rest API creada con Laravel, ya sea mediante tokens de acceso personal o mediante un esquema híbrido que combina sesión y cookies. Antes de ver cómo se implementa en la práctica, conviene entender bien qué es Sanctum y qué dos esquemas de autenticación ofrece.
Usando Laravel Sanctum nos aseguramos de que solo los usuarios autenticados puedan acceder a las rutas que lo requieran, devolviendo automáticamente un error 401 Unauthorized para las solicitudes sin credenciales válidas.
La idea detrás de Sanctum es proteger las APIs de las aplicaciones Laravel mediante autenticación y tokens de acceso. Al hacerlo, se garantiza que solo se permitan solicitudes autenticadas y autorizadas, lo que asegura la integridad de la aplicación y los datos involucrados.
Laravel Sanctum nos ofrece dos esquemas principales para trabajar, y elegir el correcto depende de tu tipo de cliente:
¿Qué es Laravel Sanctum?
Laravel Sanctum es el paquete de autenticación liviano de Laravel que proporciona dos mecanismos distintos: la autenticación para aplicaciones de página única (SPA) —basada en cookies y sesión— y la autenticación mediante tokens de API personales (Personal Access Tokens). Sanctum permite generar y administrar tokens de API: el backend los emite a través de un endpoint de login, el cliente los almacena y los envía en la cabecera Authorization: Bearer <token> en cada solicitud subsiguiente para acceder a los recursos protegidos.
Estos tokens son muy versátiles, ya que puedes usarlos para autenticar solicitudes de API, restringir el acceso a rutas específicas e incluso asignarles habilidades (scopes) para controlar con granularidad qué acciones puede realizar cada token. Desde Laravel, siempre puedes identificar al usuario que realiza la petición y aplicar las trazas o limitantes que necesites.
1 Autenticación vía SPA
Este esquema es útil cuando tienes una SPA (Single Page Application) — por ejemplo, construida con Vue o React — y quieres autenticar al usuario y proteger tu Rest API. En lugar de emitir un token, se basa en un mecanismo de sesión y cookies que Sanctum gestiona de forma transparente. Es el esquema más sencillo de configurar de los dos:
A nivel del kernel, debes habilitar el middleware de Sanctum para el grupo api, que se encarga de garantizar que las peticiones desde el frontend sean con estado (stateful):
//App/Http/Kernel
//***
class Kernel extends HttpKernel
{
//***
'api' => [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
A continuación, debes especificar el dominio que empleará la autenticación basada en cookies en el archivo config/sanctum.php, concretamente en la clave stateful. Esta lista de dominios se lee preferentemente desde la variable de entorno SANCTUM_STATEFUL_DOMAINS definida en tu archivo .env:
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1,laraprimerospasos.test',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
Si estás desarrollando en Mac o Linux con Laravel Sail, generalmente no necesitas modificar este archivo, ya que localhost ya está incluido por defecto. Para producción, simplemente añade el dominio de tu aplicación a la variable SANCTUM_STATEFUL_DOMAINS en el .env.
En el archivo config/cors.php debes habilitar la clave supports_credentials en true. Esto es necesario para que el navegador envíe las cookies de sesión junto con las peticiones cross-origin a tu API.
Para utilizar Axios en el frontend, busca el archivo resources/js/bootstrap.js y agrega la siguiente línea:
axios.defaults.withCredentials = true;Esto le indica a Axios que incluya las cookies con cada petición, permitiendo que Sanctum identifique al usuario de forma transparente sin necesidad de un token explícito en el header.
Finalmente, protege tus rutas usando el middleware auth:sanctum. De manera ejemplificada, protegeremos un conjunto de rutas en routes/api.php:
Route::group(['middleware' => 'auth:sanctum'], function () {
Route::resource('category', CategoryController::class)->except(["create", "edit"]);
Route::resource('post', PostController::class)->except(["create", "edit"]);
});Con esto ya está todo listo: el usuario solo necesita iniciar sesión una vez y Sanctum gestionará la sesión automáticamente. Este esquema encaja perfectamente con SPAs que residen en el mismo dominio o subdominio de la API.
2 En base a API Tokens
El segundo esquema es el más tradicional y el más común para proteger APIs consumidas por clientes móviles, servicios de terceros o SPAs en dominios distintos. Aquí, al momento de autenticarse, el backend genera un token de acceso personal que el cliente debe almacenar y enviar en cada petición.
Si utilizabas el esquema anterior (SPA) y quieres cambiar a tokens, primero deshabilita el middleware EnsureFrontendRequestsAreStateful:
'api' => [ //\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
***
],Generar tokens de acceso
Cabe mencionar que Sanctum ya viene instalado en los proyectos modernos de Laravel (desde Laravel 11 está integrado por defecto), por lo que normalmente no necesitas instalarlo manualmente. Si trabajas con un proyecto más antiguo, puedes instalarlo con Composer.
En nuestro controlador REST para gestionar el usuario, en el contexto de nuestro curso de Laravel, el archivo sería:
app\Http\Controllers\Api\UserController.php
Crearemos una función de login que utiliza Auth::attempt() para verificar las credenciales provistas en la petición:
$credentials = [
'email' => $request->email,
'password' => $request->password
];Una vez validadas las credenciales, generamos el token con createToken() y devolvemos su representación en texto plano mediante la propiedad plainTextToken. Es importante entender que este es el único momento en que se puede leer el token completo; después, Sanctum solo almacena su hash en la base de datos:
Auth::user()->createToken('myapptoken')->plainTextToken;A continuación, el código completo del método de login:
public function login(Request $request)
{
$credentials = [
'email' => $request->email,
'password' => $request->password
];
if (Auth::attempt($credentials)) {
$token = Auth::user()->createToken('myapptoken')->plainTextToken;
session()->put('token', $token);
return response()->json($token);
}
return response()->json("Usuario y/o contraseña inválido", 422);
}Lo más importante es notar que, aunque Sanctum provee todos los mecanismos para generar y validar el token, la lógica de emisión —cuándo se crea, con qué nombre, con qué habilidades— depende completamente del desarrollador. Esto te da total control sobre la estrategia de autenticación de tu API.
Para crear el controlador desde Artisan:
$ php artisan make:controller Api/UserControllerCon el siguiente contenido completo:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class UserController extends Controller
{
public function login(Request $request)
{
$credentials = [
'email' => $request->email,
'password' => $request->password
];
if(Auth::attempt($credentials)){
$token = Auth::user()->createToken('myapptoken')->plainTextToken;
return response()->json($token);
}
return response()->json("Usuario y/o contraseña inválido");
}
}Recuerda: Sanctum ya viene instalado en los proyectos modernos de Laravel.
Más ejemplos con Sanctum
Aquí te dejo otros ejemplos con autenticación en Laravel Sanctum y protección de rutas.
En el caso de que el proyecto no disponga de Sanctum, instálalo mediante Composer:
composer require laravel/sanctumLuego, publica los archivos de configuración y las migraciones necesarias:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrateEl comando vendor:publish generará el archivo config/sanctum.php con todas las opciones configurables, y migrate creará la tabla personal_access_tokens en tu base de datos, que es donde Sanctum almacena los hashes de los tokens emitidos.
A continuación, ya puedes proteger tus rutas usando el middleware auth:sanctum en tu archivo routes/api.php:
Route::middleware('auth:sanctum')->group(function () {
Route::get('/post', function () {
return view('dashboard');
});
});En este ejemplo, la ruta /post solo será accesible si el usuario está autenticado mediante el middleware de Sanctum. Cualquier petición sin un token válido recibirá automáticamente una respuesta 401 Unauthorized.
Puedes generar un token de API para un usuario autenticado con una sola línea:
$token = $user->createToken('token-name')->plainTextToken;Este código genera un token de API para el usuario dado con el nombre token-name. Luego, el cliente lo envía en la cabecera Authorization: Bearer <token> para autenticar sus solicitudes. Aquí tienes otro ejemplo completo de endpoint de login con Sanctum en routes/api.php:
use App\\Models\\User;
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\Hash;
Route::post('/login', function (Request $request) {
$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)->plainTextToken;
return ['token' => $token];
});Nota que en este ejemplo se utiliza $request->device_name como nombre del token, lo que permite identificar desde qué dispositivo se emitió cada token. Esta práctica es especialmente útil cuando el usuario gestiona sus sesiones activas desde su perfil.
Estrategias para Renovar el Token de Acceso en una Rest API con Laravel Sanctum
Laravel Sanctum es la herramienta estándar dentro del ecosistema Laravel para gestionar la autenticación en aplicaciones modernas. Al construir plataformas híbridas —que integran clientes web como aplicaciones de página única (SPA) en Vue y clientes móviles nativos—, es indispensable diseñar una estrategia sólida para la administración, persistencia y ciclo de vida de los tokens de acceso personal (Personal Access Tokens).
A diferencia de los esquemas tradicionales de sesión basados exclusivamente en la web, la coexistencia de múltiples dispositivos introduce retos de seguridad y rendimiento que deben resolverse directamente en la arquitectura del backend.
1. Autenticación Híbrida: Cookies frente a Tokens
Sanctum ofrece un enfoque dual para proteger los recursos de la API, permitiendo combinar dos mecanismos de forma paralela:
- Autenticación basada en Cookies (Stateful): Diseñada específicamente para SPAs o aplicaciones web alojadas en el mismo dominio de primer nivel. Emplea cookies encriptadas junto con protección contra ataques CSRF. Este método es el que viene activo por defecto para el entorno de escritorio y requiere el middleware
EnsureFrontendRequestsAreStateful. - Autenticación basada en Tokens (Bearer Tokens): Obligatoria para dispositivos móviles o servicios externos de terceros que no comparten el almacenamiento de cookies del navegador. El cliente recibe un token en texto plano (
plainTextToken) tras validar sus credenciales, y debe adjuntarlo en la cabeceraAuthorization: Bearer <token>de cada petición HTTP.
El servidor procesa ambas vías de forma automática a través del middleware de Sanctum, traduciendo el origen en una única instancia del usuario autenticado que puedes obtener con $request->user().
2. El Ciclo de Vida del Token: Mitigación de Registros Muertos
Por defecto, Sanctum permite que los tokens creados tengan una vigencia indefinida si la directiva expiration en el archivo config/sanctum.php se define como null.
config/sanctum.php
'expiration' => null, // El token no expira por defectoAunque este enfoque simplifica el desarrollo inicial, representa una debilidad en términos de infraestructura y seguridad. Si los usuarios inician sesión con frecuencia desde múltiples navegadores o dispositivos, la tabla personal_access_tokens acumulará miles de registros obsoletos ("registros muertos") que ralentizarán las consultas en la base de datos al no existir un proceso de depuración automática.
Para solventar este problema, existen tres estrategias principales de diseño:
Estrategia Cero: Depuración Programada (Cron Jobs)
Consiste en mantener los tokens sin fecha de expiración explícita en la configuración de Sanctum, pero delegando la limpieza a un proceso en segundo plano de Laravel (Scheduled Tasks). El script analiza periódicamente la columna last_used_at y elimina de forma definitiva aquellos registros que no hayan registrado actividad en un rango de varios meses.
Estrategia Uno: Renovación Activa (Silent Refresh)
Esta técnica establece un periodo de expiración estricto en la configuración (por ejemplo, 30 días). El cliente (ya sea en Vue o Flutter) cuenta con un endpoint dedicado en la API que, antes de alcanzar la fecha límite, invalida el token actual y genera uno nuevo, actualizando el almacenamiento local del dispositivo (localStorage o SharedPreferences) de forma transparente para el usuario.
Estrategia Dos: Extensión Dinámica del Token
Es la estrategia adoptada en arquitecturas de alta disponibilidad. En lugar de rotar la cadena de texto plano del token, el backend extiende dinámicamente la validez del registro modificando sus metadatos internos cada vez que el usuario consume la API. De este modo, una sesión que se utiliza a diario nunca expira, pero se invalida automáticamente si el dispositivo deja de realizar peticiones durante un tiempo prolongado.
3. Implementación Práctica: Validación Temprana y Extensión de Sesión
Independientemente de la estrategia seleccionada, el principio fundamental de la arquitectura de clientes dictamina que la aplicación debe verificar la integridad de la sesión en su primer arranque. No se debe esperar a que el usuario intente realizar una acción crítica (como una descarga o una compra) para descubrir que su token ha sido revocado, ya que esto degrada la experiencia de usuario con errores inesperados en cascada.
Tanto en entornos web como móviles, se debe consumir un endpoint ligero de verificación (por ejemplo, /api/v1/user-info) en los ganchos de inicialización globales del ciclo de vida (App.vue o el método main() de Flutter). Si el servidor retorna un código de estado 401 Unauthorized, el cliente intercepta la respuesta, limpia las variables de estado locales de inmediato y redirige al formulario de inicio de sesión.
A continuación, se detalla la implementación en el backend para un endpoint de verificación híbrido que extiende el ciclo de vida del token mediante la manipulación forzada del registro:
use Illuminate\Http\Request;
use Laravel\Sanctum\TransientToken;
use Laravel\Sanctum\PersonalAccessToken;
Route::middleware('auth:sanctum')->get('/v1/user-info', function (Request $request) {
$user = $request->user();
$currentToken = $user->currentAccessToken();
// Comprobamos que el token provenga de la base de datos y no de una cookie (TransientToken)
if ($currentToken instanceof PersonalAccessToken) {
/**
* Forzamos la actualización de la fecha de creación.
* Al desplazar 'created_at' al instante actual, extendemos de forma
* automática la ventana de expiración de 30 días configurada en Sanctum.
*/
$currentToken->forceFill([
'created_at' => now(),
])->save();
}
return response()->json([
'success' => true,
'data' => $user,
]);
});Justificación de la Verificación de Instancia
La validación instanceof PersonalAccessToken es obligatoria en infraestructuras híbridas. Cuando el usuario accede desde el cliente web mediante cookies, el método currentAccessToken() retorna un objeto de tipo TransientToken. Dado que este objeto no reside físicamente en la tabla de la base de datos, carece del método forceFill(). Intentar actualizarlo provocaría un error fatal en la ejecución del backend.
4. Centralización e Identificación Única de Dispositivos
Para implementar políticas comerciales restrictivas —como limitar el número de sesiones simultáneas permitidas en cuentas compartidas—, es imprescindible centralizar la lógica de emisión de tokens en un único servicio o clase controladora.
Al momento de generar un token a través del método createToken(), se debe evitar el uso de cadenas genéricas o estáticas. En su lugar, se analiza el encabezado User-Agent de la petición HTTP entrante para registrar el nombre del navegador o el entorno de ejecución nativo del cliente:
public function generateDeviceToken(Request $request, User $user): string
{
$userAgent = $request->header('User-Agent', 'Generic Device');
$deviceName = 'Unknown Device';
// Clasificación modular del origen de la petición
if (str_contains($userAgent, 'Dart')) {
$deviceName = 'Mobile Application (Flutter)';
} elseif (str_contains($userAgent, 'Firefox')) {
$deviceName = 'Mozilla Firefox';
} elseif (str_contains($userAgent, 'Chrome')) {
$deviceName = 'Google Chrome';
} elseif (str_contains($userAgent, 'Safari')) {
$deviceName = 'Apple Safari';
}
// Limitación de longitud para cumplir con las restricciones del esquema de la tabla
$sanitizedName = substr($deviceName, 0, 180);
// Centralización de reglas de negocio antes de la persistencia
return $user->createToken($sanitizedName)->plainTextToken;
}Limitaciones del Control por Dispositivo
Aunque registrar el nombre del navegador aporta claridad al usuario en sus paneles de configuración y le permite revocar accesos específicos, no es un identificador unívoco infalible para aplicar bloqueos estrictos. Si un usuario opera de forma legítima desde dos ordenadores distintos usando el mismo navegador (por ejemplo, Google Chrome en su equipo de trabajo y en su equipo personal), ambas peticiones compartirán el mismo nombre de dispositivo.
Si la lógica de negocio exige bloquear o destruir sesiones duplicadas basándose únicamente en esa etiqueta, se cerrará la sesión válida del segundo dispositivo de manera errónea. Para escenarios que requieran una firma de identidad única por hardware, el cliente móvil debe extraer el identificador único del dispositivo (UUID) a nivel de sistema operativo y transmitirlo explícitamente en los parámetros de la petición de autenticación hacia la API.
Ahora, conoce a fondo el motor de plantillas de Laravel, Blade.
Código fuente:
https://github.com/libredesarrollo/book-course-laravel-base-api-11/releases/tag/v0.1