feat(auth): harden authentication and add configurable two-factor support
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Notifications\TwoFactorFailedNotification;
|
||||
use App\Services\Auth\SecurityEventRecorder;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -19,7 +21,9 @@ class TwoFactorChallengeController extends Controller
|
||||
*/
|
||||
public function create(TwoFactorLoginRequest $request): Response|RedirectResponse
|
||||
{
|
||||
if (! config('auth-ui.features.two_factor') || ! $request->hasChallengedUser()) {
|
||||
if (! config('auth-ui.features.two_factor')
|
||||
|| ! $request->hasChallengedUser()
|
||||
|| $this->challengeHasExpired($request)) {
|
||||
$this->clearChallenge($request);
|
||||
|
||||
return redirect()->route('login');
|
||||
@@ -31,9 +35,13 @@ class TwoFactorChallengeController extends Controller
|
||||
/**
|
||||
* Complete the pending login using a TOTP or recovery code.
|
||||
*/
|
||||
public function store(TwoFactorLoginRequest $request): RedirectResponse
|
||||
{
|
||||
if (! config('auth-ui.features.two_factor') || ! $request->hasChallengedUser()) {
|
||||
public function store(
|
||||
TwoFactorLoginRequest $request,
|
||||
SecurityEventRecorder $securityEvents
|
||||
): RedirectResponse {
|
||||
if (! config('auth-ui.features.two_factor')
|
||||
|| ! $request->hasChallengedUser()
|
||||
|| $this->challengeHasExpired($request)) {
|
||||
$this->clearChallenge($request);
|
||||
|
||||
return redirect()->route('login');
|
||||
@@ -51,6 +59,12 @@ class TwoFactorChallengeController extends Controller
|
||||
$user->replaceRecoveryCode($recoveryCode);
|
||||
} elseif (! $request->hasValidCode()) {
|
||||
event(new TwoFactorAuthenticationFailed($user));
|
||||
$user->notify(new TwoFactorFailedNotification(
|
||||
(string) $request->ip(),
|
||||
(string) $request->userAgent(),
|
||||
now()->toIso8601String()
|
||||
));
|
||||
$securityEvents->record('login.two_factor_failed', $user, $request);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
$request->filled('recovery_code') ? 'recovery_code' : 'code' => 'The provided authentication code was invalid.',
|
||||
@@ -61,12 +75,24 @@ class TwoFactorChallengeController extends Controller
|
||||
|
||||
Auth::login($user, $request->remember());
|
||||
$request->session()->regenerate();
|
||||
$securityEvents->record('login.two_factor_succeeded', $user, $request, [
|
||||
'used_recovery_code' => (bool) $recoveryCode,
|
||||
]);
|
||||
|
||||
return redirect()->intended(config('auth-ui.redirects.login', '/dashboard'));
|
||||
}
|
||||
|
||||
private function clearChallenge(TwoFactorLoginRequest $request): void
|
||||
{
|
||||
$request->session()->forget(['login.id', 'login.remember']);
|
||||
$request->session()->forget(['login.id', 'login.remember', 'login.started_at']);
|
||||
}
|
||||
|
||||
private function challengeHasExpired(TwoFactorLoginRequest $request): bool
|
||||
{
|
||||
$startedAt = $request->session()->get('login.started_at');
|
||||
|
||||
return ! is_int($startedAt)
|
||||
|| now()->timestamp - $startedAt
|
||||
> config('auth-ui.security.two_factor_challenge_timeout');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user