Descripción
Cierra la sesión del usuario autenticado, revocando su token Sanctum y eliminando la sesión PHP. El token usado en la solicitud queda inválido inmediatamente.
Características
Revocación de token : El token Sanctum se elimina de la base de datos
Limpieza de sesión PHP : Invalida la sesión web (para exportaciones)
Regeneración de CSRF : Genera nuevo token CSRF para prevenir ataques
Instantáneo : El token deja de funcionar inmediatamente
Request
Bearer token obtenido en el login Authorization: Bearer 1|abc123def456ghi789jklmnopqrstuvwxyz
Si el token ya fue revocado o es inválido, recibirás un error 401 antes de llegar al endpoint.
Body
Este endpoint no requiere body (vacío).
Response
Siempre true si se ejecutó correctamente
Mensaje de confirmación: "Sesión cerrada correctamente"
Ejemplos
cURL
Fetch API
Axios
Python (requests)
PHP (Guzzle)
curl -X POST "https://tu-dominio.com/api/logout" \
-H "Authorization: Bearer 1|abc123def456ghi789jklmnopqrstuvwxyz"
Respuestas
200 OK - Logout Exitoso
{
"success" : true ,
"message" : "Sesión cerrada correctamente"
}
401 Unauthorized - Token Inválido
{
"message" : "Unauthenticated."
}
Este error es lanzado por el middleware auth:sanctum si el token no existe o ya fue revocado.
Implementación Interna
Código del Controlador
app/Http/Controllers/Api/AuthController.php:97
public function logout ( Request $request )
{
// Revocar el token oficial de Sanctum
$request -> user () -> currentAccessToken () -> delete ();
// Cerrar sesión de Laravel (PHP Session)
\Illuminate\Support\Facades\ Auth :: logout ();
$request -> session () -> invalidate ();
$request -> session () -> regenerateToken ();
return response () -> json ([
'success' => true ,
'message' => 'Sesión cerrada correctamente'
]);
}
Pasos de Ejecución
Revocación del token Sanctum
$request -> user () -> currentAccessToken () -> delete ();
Elimina el token de la tabla personal_access_tokens. El token se vuelve inválido inmediatamente.
Cierre de sesión PHP
Desautentica al usuario de la sesión web de Laravel.
Invalidación de sesión
$request -> session () -> invalidate ();
Elimina todos los datos de la sesión PHP actual.
Regeneración de token CSRF
$request -> session () -> regenerateToken ();
Genera un nuevo token CSRF para prevenir ataques de sesión.
Respuesta exitosa
Retorna JSON confirmando el cierre de sesión.
Limpieza en Frontend
Después de un logout exitoso, el frontend debe:
Eliminar token localStorage . removeItem ( 'auth_token' );
// o
sessionStorage . removeItem ( 'auth_token' );
Limpiar datos de usuario localStorage . removeItem ( 'user' );
localStorage . removeItem ( 'permissions' );
localStorage . removeItem ( 'empresas' );
Resetear estado global // Zustand
authStore . getState (). reset ();
// Redux
dispatch ( logout ());
// Pinia
authStore . $reset ();
Redirigir a login window . location . href = '/login' ;
// o
router . push ( '/login' );
Casos de Uso
Logout Voluntario
Usuario hace clic en “Cerrar Sesión”:
const handleLogout = async () => {
try {
const response = await fetch ( '/api/logout' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ localStorage . getItem ( 'auth_token' ) } `
}
});
if ( response . ok ) {
// Limpiar y redirigir
localStorage . clear ();
window . location . href = '/login' ;
}
} catch ( error ) {
console . error ( 'Error al cerrar sesión:' , error );
}
};
Logout por Token Expirado
Cuando una petición retorna 401, cerrar sesión automáticamente:
import axios from 'axios' ;
const api = axios . create ({
baseURL: '/api' ,
});
// Interceptor de respuesta
api . interceptors . response . use (
( response ) => response ,
( error ) => {
if ( error . response ?. status === 401 ) {
// Token expirado o inválido
localStorage . clear ();
window . location . href = '/login' ;
}
return Promise . reject ( error );
}
);
export default api ;
Logout al Cambiar de Rol
Cuando un admin cambia los permisos de un rol activo:
const updateRolePermissions = async ( rolId , permissions ) => {
await api . put ( `/permissions/role/ ${ rolId } ` , { permissions });
// Si el usuario actual tiene ese rol, hacer logout
if ( currentUser . rol_id === rolId ) {
await handleLogout ();
alert ( 'Tus permisos han cambiado. Por favor, inicia sesión nuevamente.' );
}
};
Seguridad
Prevención de Reutilización
Una vez revocado, el token no puede ser reutilizado:
# Primer uso (exitoso)
curl -X POST "/api/logout" -H "Authorization: Bearer {token}"
# Respuesta: 200 OK
# Segundo uso (fallido)
curl -X POST "/api/logout" -H "Authorization: Bearer {token}"
# Respuesta: 401 Unauthorized
Revocación en Base de Datos
El token se elimina físicamente de personal_access_tokens:
DELETE FROM personal_access_tokens
WHERE id = ( SELECT tokenable_id FROM ... WHERE token = 'hash_del_token' );
Cierre de Todas las Sesiones
Para cerrar sesión en todos los dispositivos :
// Revocar TODOS los tokens del usuario
$user -> tokens () -> delete ();
Esto es útil cuando:
El usuario cambia su contraseña
Se detecta actividad sospechosa
El usuario lo solicita explícitamente
Diferencias con Refresh
Aspecto Logout Refresh Propósito Terminar sesión Renovar token Token actual Se elimina Se elimina Token nuevo No se genera Se genera Sesión PHP Se invalida Se mantiene Redirección A login Ninguna Uso Cerrar sesión voluntario Mantener sesión activa
Códigos de Estado
Código Descripción Causa 200 OK Logout exitoso 401 Unauthorized Token inválido o ya revocado 500 Internal Server Error Error de base de datos
Mejores Prácticas
Confirmación de Usuario Muestra un modal de confirmación antes de cerrar sesión: const confirmLogout = () => {
if ( confirm ( '¿Estás seguro que deseas cerrar sesión?' )) {
handleLogout ();
}
};
Guardado Automático Guarda cambios pendientes antes de cerrar sesión: const safeLogout = async () => {
if ( hasUnsavedChanges ()) {
await saveChanges ();
}
await handleLogout ();
};
Limpieza Completa Elimina TODOS los datos sensibles del navegador: localStorage . clear ();
sessionStorage . clear ();
// Limpiar cookies si es necesario
Logging de Auditoría Registra el evento de logout en logs del servidor: Log :: info ( 'Usuario cerró sesión' , [
'user_id' => $user -> id ,
'ip' => $request -> ip (),
'user_agent' => $request -> userAgent ()
]);
Testing
PHPUnit
tests/Feature/LogoutTest.php
class LogoutTest extends TestCase
{
use RefreshDatabase ;
public function test_user_can_logout ()
{
$user = User :: factory () -> create ();
$token = $user -> createToken ( 'test_token' ) -> plainTextToken ;
$response = $this -> withHeaders ([
'Authorization' => 'Bearer ' . $token
]) -> postJson ( '/api/logout' );
$response -> assertStatus ( 200 )
-> assertJson ([
'success' => true ,
'message' => 'Sesión cerrada correctamente'
]);
// Verificar que el token fue eliminado
$this -> assertDatabaseMissing ( 'personal_access_tokens' , [
'tokenable_id' => $user -> id ,
'tokenable_type' => User :: class
]);
}
public function test_cannot_logout_without_token ()
{
$response = $this -> postJson ( '/api/logout' );
$response -> assertStatus ( 401 );
}
public function test_cannot_reuse_revoked_token ()
{
$user = User :: factory () -> create ();
$token = $user -> createToken ( 'test_token' ) -> plainTextToken ;
// Primer logout (exitoso)
$this -> withHeaders ([ 'Authorization' => 'Bearer ' . $token ])
-> postJson ( '/api/logout' )
-> assertStatus ( 200 );
// Intentar usar el token nuevamente (fallido)
$this -> withHeaders ([ 'Authorization' => 'Bearer ' . $token ])
-> postJson ( '/api/logout' )
-> assertStatus ( 401 );
}
}