A07: Authentication Failures (Blog)
What is an authentication failure?
Authentication Failures happen when login, session management, password resets, or token-based authentication are implemented or configured in a way that attackers can bypass, abuse, or persist access.
Laravel 12 starter kits apply login throttling by default, session IDs should be regenerated to prevent session fixation, Fortify supports two-factor authentication, and Laravel’s password broker handles password reset flows. Sanctum supports token abilities and is recommended for many first-party SPA/API cases.
Typical Laravel failure
- Weak login throttling.
- No MFA.
- Bad password reset flow.
- Long-lived sessions with poor invalidation.
- Mixing SPA/API auth incorrectly.
Impact
- Account takeover via credential stuffing / brute force.
- Persistent sessions after password change or logout.
- Reset-token abuse and takeover.
- Privilege escalation when tokens/sessions are mixed incorrectly.
Laravel 12 remediation
- Use Fortify or starter kits instead of inventing auth.
- Enable 2FA.
- Enforce password confirmation for sensitive account changes.
- Regenerate sessions on login and invalidate on logout/password change.
- For APIs, use Sanctum abilities (or Passport only when OAuth2 complexity is actually needed).
- Separate browser session auth from token auth correctly.
For a blog:
- Reading posts/comments should be public (no auth required).
- Comment creation should require authentication (
authmiddleware / Form Request authorize). - Post creation should require stronger authorization (role/permission).
Concrete fix
Session hardening (prevent fixation, improve invalidation):
$request->session()->regenerate(); // On logout// Auth::logout();// $request->session()->invalidate();// $request->session()->regenerateToken();
Fortify (enable 2FA at the framework level):
// config/fortify.php use Laravel\Fortify\Features; 'features' => [ // ... Features::twoFactorAuthentication(),],
Passwords (use the password broker, not custom reset tokens):
use Illuminate\Support\Facades\Password; Password::sendResetLink(['email' => $request->string('email')->toString()]);
Sanctum (token abilities for API access):
$token = $user->createToken('api', ['orders:read', 'orders:write'])->plainTextToken;
Design pattern angle
Keep browser session auth and API token auth as separate use-case entry points. Use an Action boundary that depends on an auth context (session user vs token user) instead of mixing both in one 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 { // invariant: require password confirmation for session flows if ($auth->type() === 'session') { // e.g. middleware('password.confirm') at the route level } $auth->user()->forceFill(['email' => $newEmail])->save(); }} // Usage examples // Web route/controller (session auth)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 (token auth)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();}