resources/views/pages/frontend/docs/sso-autenticacion.md
Última modificación: 24 Oct, 2026

Defensa Perimetral, Sesión Única & 2FA

La capa de identidad de Mañuti implementa un esquema de seguridad bancaria orientado a mitigar el secuestro de sesiones, ataques de fuerza bruta distribuidos y reutilización indebida de credenciales concurrentes.


1. Autenticación Dual & Cierre de Sesión Criptográfico

El punto de acceso intercepta identificadores heterogéneos sin revelar la existencia previa del registro:

flowchart TD
    INPUT["[Input del Usuario]"]
    VALIDATE{"¿filter_var(...,<br>FILTER_VALIDATE_EMAIL)?"}
    CORREO["[Columna: correo]"]
    USUARIO["[Columna: usuario]"]
    HASH["[Hash::check(Bcrypt)]"]

    INPUT --> VALIDATE
    VALIDATE -->|"Sí"| CORREO
    VALIDATE -->|"No"| USUARIO
    
    CORREO --> HASH
    USUARIO --> HASH

Protocolo de Salida Segura (Anti-CSRF)

Queda estrictamente prohibido el logout mediante verbos GET. El cierre de sesión requiere POST protegido con token CSRF validado:

public function logout(Request $request): RedirectResponse
{
    Auth::logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();

    return redirect('/login');
}

2. Política de Sesión Única (Single Session Policy) & Kill Switch por WebSockets

Mañuti impone concurrencia cero por cuenta de usuario. La coexistencia de dos terminales activas bajo la misma identidad está prohibida a nivel de protocolo.

Arquitectura de Espejo Relacional (login)

Mientras que el payload de la sesión reside en memoria volátil dentro de Redis (SESSION_DRIVER=redis), la tabla relacional login mantiene un puntero sincronizado al identificador de la sesión activa:

CREATE TABLE `login` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `usuario_id` BIGINT UNSIGNED NOT NULL,
    `session_id` VARCHAR(255) NOT NULL UNIQUE,
    `ip_address` VARCHAR(45) NULL,
    `user_agent` TEXT NULL,
    `last_activity` TIMESTAMP NOT NULL,
    `open` TINYINT(1) DEFAULT 1,
    FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE CASCADE
);

Ciclo de Expulsión Remota Reactiva

Cuando se detecta un nuevo inicio de sesión o un administrador ejecuta una revocación en /sesiones-activas:

  1. El servidor actualiza login.open = 0 en MariaDB.
  2. El backend purga el ID en Redis: Redis::del('laravel-cache-:' . $session_id).
  3. Se despacha un evento en tiempo real a través de Laravel Reverb:
    UserSessionTerminated::dispatch($usuario->id);
    
  4. El navegador de la terminal desplazada escucha en el canal privado private-user.{id} y fuerza la redirección inmediata hacia /login incluso si el usuario se encuentra navegando en páginas 404 mediante el blindaje de la Ruta Fallback.
// resources/js/layout/global-listeners.js
window.Echo.private('user.' + userId)
    .listen('.session.terminated', (e) => {
        window.location.href = '/administracion';
    });

3. Rate Limiting Dinámico & Bloqueo Progresivo

Para neutralizar ataques de diccionario o fuerza bruta:

  1. Clave de Throttle Compuesta: Str::lower($request->input('login')) . '|' . $request->ip().
  2. Umbral de Disparo: Al alcanzar 5 fallos consecutivos (parametrizables en configuracion_sistema), la cuenta transiciona visualmente en MariaDB al catálogo USR_BLOQUEO_TEMP.
  3. Registro Forense en MongoDB: El evento dispara de forma asíncrona el log sistema_bloqueo_usuario con código HTTP 429 Too Many Requests.
  4. Lazy Unlock (Desbloqueo Perezoso): Transcurridos los 180 segundos de enfriamiento (evaluados en Redis), el sistema restaura el estatus a USR_ACTIVO de forma transparente únicamente si el siguiente intento ingresa la contraseña válida.

4. Segundo Factor de Autenticación (2FA TOTP RFC 6238)

El módulo 2FA se integra de forma desacoplada mediante algoritmos TOTP compatibles con Google Authenticator y Microsoft Authenticator.

Almacenamiento Seguro de Secretos

Los secretos TOTP y los códigos de respaldo de emergencia se resguardan mediante cifrado simétrico AES-256-CBC:

// app/Models/Usuario.php
public function setTwoFactorSecretAttribute($value): void
{
    $this->attributes['two_factor_secret'] = $value ? Crypt::encryptString($value) : null;
}

public function getTwoFactorSecretAttribute($value): ?string
{
    return $value ? Crypt::decryptString($value) : null;
}

Códigos de Recuperación Dinámicos

  • La cantidad de códigos generados (Str::random(10) . '-' . Str::random(10)) se parametriza globalmente mediante la clave security_2fa_codes_count.
  • El consumo de un código de recuperación es de uso único (burn on use), purgado de la colección encriptada al procesar el acceso.
  • En caso de pérdida física del dispositivo móvil, el Super Admin dispone del bypass auditado users.reset_2fa, revocando la configuración para exigir un enrolamiento limpio en el próximo ciclo de login.

5. Bloqueo de Pantalla por Inactividad (Idle Lock Screen)

El frontend monitorea continuamente la inactividad del operador a través de eventos de entrada (mousemove, keydown, scroll, visibilitychange).

flowchart TD
    ACT["[Actividad Normal]"]
    TIMER["Timer: 15 min (security_idle_timeout)"]
    MODAL["[Modal de Advertencia: 60 segs]"]
    RESET["[Reset Timer]"]
    LOCK["[POST /api/system/lock]"]
    SCREEN["[Pantalla /bloqueo]"]

    ACT --> TIMER
    TIMER --> MODAL
    
    MODAL -->|"Clic 'Mantener Activa'"| RESET
    MODAL -->|"Timeout cumplido"| LOCK
    
    LOCK --> SCREEN
  • Al transcurrir la ventana de advertencia, el frontend envía una petición atómica que marca session(['is_locked' => true]) y redirige a la vista aislada /bloqueo.
  • El middleware CheckSessionLock intercepta el tráfico administrativo, exigiendo la revalidación de la contraseña del usuario en sesión (Hash::check) para restaurar la navegación exactamente en la URL previa.

Enlaces Relacionados

Edita este documento en >> sso-autenticacion.md
← Volver a la Landing Page