Skip Content

A07: Fallos de Autenticacion (Blog)

¿Que es un fallo de autenticacion?

Los Fallos de Autenticacion ocurren cuando el login, el manejo de sesiones, el reset de passwords o la autenticacion por tokens se implementan/configuran de forma que un atacante pueda evadir controles o mantener acceso.

Los starter kits de Laravel 12 aplican throttling por defecto, las sesiones deben regenerar el ID para prevenir session fixation, Fortify soporta 2FA, y el password broker de Laravel maneja flujos de reset. Sanctum soporta abilities en tokens y es recomendado para muchos casos first-party SPA/API.

Fallo tipico en Laravel

  • Throttling debil en login.
  • Sin MFA.
  • Flujo de reset de password mal disenado.
  • Sesiones de larga vida con mala invalidacion.
  • Mezclar auth de SPA/API incorrectamente.

Impacto

  • Toma de cuentas por credential stuffing / fuerza bruta.
  • Sesiones persistentes despues de logout o cambio de password.
  • Abuso de tokens de reset y takeover.
  • Escalamiento cuando se mezclan tokens/sesiones mal.

Remediacion en Laravel 12

  • Usa Fortify o starter kits en lugar de inventar auth.
  • Habilita 2FA.
  • Exige confirmacion de password para cambios sensibles.
  • Regenera sesiones al login e invalida en logout/cambio de password.
  • Para APIs, usa abilities de Sanctum (o Passport solo si necesitas OAuth2 de verdad).
  • Separa correctamente auth por sesion (browser) de auth por token.

Para un blog:

  • Lectura de posts/comentarios debe ser publica (sin auth).
  • Creacion de comentarios debe requerir autenticacion (auth middleware / Form Request authorize).
  • Creacion de posts debe requerir autorizacion mas fuerte (rol/permisos).

Arreglo concreto

Endurecimiento de sesion (evita fixation, mejora invalidacion):

$request->session()->regenerate();
 
// En logout
// Auth::logout();
// $request->session()->invalidate();
// $request->session()->regenerateToken();

Fortify (habilita 2FA a nivel framework):

// config/fortify.php
 
use Laravel\Fortify\Features;
 
'features' => [
// ...
Features::twoFactorAuthentication(),
],

Passwords (usa el password broker, no tokens custom):

use Illuminate\Support\Facades\Password;
 
Password::sendResetLink(['email' => $request->string('email')->toString()]);

Sanctum (abilities en tokens para APIs):

$token = $user->createToken('api', ['orders:read', 'orders:write'])->plainTextToken;

Enfoque de patron de diseno

Separa auth por sesion (browser) y auth por token (API) como puntos de entrada distintos del caso de uso. Usa Actions como limites y evita mezclar ambos modelos en el mismo controller.

interface AuthContext
{
public function user(): User;
public function type(): string; // 'session' | 'token'
}
 
final class SessionAuthContext implements AuthContext
{
public function __construct(private User $user) {}
 
public function user(): User
{
return $this->user;
}
 
public function type(): string
{
return 'session';
}
}
 
final class TokenAuthContext implements AuthContext
{
public function __construct(private User $user) {}
 
public function user(): User
{
return $this->user;
}
 
public function type(): string
{
return 'token';
}
}
 
final class ChangeEmailAction
{
public function handle(AuthContext $auth, string $newEmail): void
{
// invariante: exigir confirmacion de password en flujos de sesion
if ($auth->type() === 'session') {
// p.ej. middleware('password.confirm') a nivel de ruta
}
 
$auth->user()->forceFill(['email' => $newEmail])->save();
}
}
 
// Ejemplos de uso
 
// Web route/controller (auth por sesion)
public function updateEmail(Request $request, ChangeEmailAction $action)
{
$request->validate(['email' => ['required', 'email']]);
 
$action->handle(
new SessionAuthContext($request->user()),
$request->string('email')->toString(),
);
 
return back();
}
 
// API route/controller (auth por token)
public function updateEmailApi(Request $request, ChangeEmailAction $action)
{
$request->validate(['email' => ['required', 'email']]);
 
$action->handle(
new TokenAuthContext($request->user()),
$request->string('email')->toString(),
);
 
return response()->noContent();
}

Referencias