Skip to content

Commit dea801e

Browse files
committed
Adding security fixes, rate limiting and more
1 parent c1a8184 commit dea801e

File tree

4 files changed

+60
-7
lines changed

4 files changed

+60
-7
lines changed

app/Actions/TwoFactorAuth/CompleteTwoFactorAuthentication.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ class CompleteTwoFactorAuthentication
1515
*/
1616
public function __invoke($user): void
1717
{
18-
// Log the user in
19-
Auth::login($user);
18+
// Get the remember preference from the session (default to false if not set)
19+
$remember = Session::get('login.remember', false);
20+
21+
// Log the user in with the remember preference
22+
Auth::login($user, $remember);
2023

21-
// Clear the session that is used to determine if the user can visit the 2fa challenge page
22-
Session::forget('login.id');
24+
// Clear the session variables used for the 2FA challenge
25+
Session::forget(['login.id', 'login.remember']);
2326
}
2427
}

app/Actions/TwoFactorAuth/ProcessRecoveryCode.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function __invoke(array $recoveryCodes, string $submittedCode)
2626

2727
// Remove the used recovery code from the list
2828
$updatedCodes = array_values(array_filter($recoveryCodes, function($code) use ($submittedCode) {
29-
return $code !== $submittedCode;
29+
return !hash_equals($code, $submittedCode);
3030
}));
3131

3232
return $updatedCodes;

app/Http/Controllers/Auth/AuthenticatedSessionController.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ public function store(LoginRequest $request): RedirectResponse
3535

3636
// If this user exists, password is correct, and 2FA is enabled, we want to redirect to the 2FA challenge
3737
if ($user && $user->two_factor_confirmed_at && Hash::check($request->password, $user->password)) {
38-
$request->session()->put('login.id', $user->getKey());
39-
// Optionally clear any previous errors
38+
// Store the user ID and remember preference in the session
39+
$request->session()->put([
40+
'login.id' => $user->getKey(),
41+
'login.remember' => $request->boolean('remember')
42+
]);
43+
4044
return redirect()->route('two-factor.challenge');
4145
}
4246

app/Http/Controllers/Auth/TwoFactorAuthChallengeController.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
use App\Http\Controllers\Controller;
88
use App\Models\User;
99
use Illuminate\Http\Request;
10+
use Illuminate\Support\Facades\RateLimiter;
11+
use Illuminate\Validation\ValidationException;
12+
use Illuminate\Support\Str;
1013

1114
class TwoFactorAuthChallengeController extends Controller
1215
{
@@ -26,6 +29,9 @@ public function store(Request $request)
2629
// If we made it here, user is available via the EnsureTwoFactorChallengeSession middleware
2730
$user = $request->two_factor_auth_user;
2831

32+
// Ensure the 2FA challenge is not rate limited
33+
$this->ensureIsNotRateLimited($user);
34+
2935
// Handle one-time password (OTP) code
3036
if ($request->filled('code')) {
3137
return $this->authenticateUsingCode($request, $user);
@@ -53,9 +59,11 @@ protected function authenticateUsingCode(Request $request, User $user)
5359

5460
if ($valid) {
5561
app(CompleteTwoFactorAuthentication::class)($user);
62+
RateLimiter::clear($this->throttleKey($user));
5663
return redirect()->intended(route('dashboard', absolute: false));
5764
}
5865

66+
RateLimiter::hit($this->throttleKey($user));
5967
return back()->withErrors(['code' => __('The provided two factor authentication code was invalid.')]);
6068
}
6169

@@ -75,6 +83,7 @@ protected function authenticateUsingRecoveryCode(Request $request, User $user)
7583

7684
// If ProcessRecoveryCode returns false, the code was invalid
7785
if ($updatedCodes === false) {
86+
RateLimiter::hit($this->throttleKey($user));
7887
return back()->withErrors(['recovery_code' => __('The provided two factor authentication recovery code was invalid.')]);
7988
}
8089

@@ -84,8 +93,45 @@ protected function authenticateUsingRecoveryCode(Request $request, User $user)
8493
// Complete the authentication process
8594
app(CompleteTwoFactorAuthentication::class)($user);
8695

96+
// Clear rate limiter after successful authentication
97+
RateLimiter::clear($this->throttleKey($user));
98+
8799
// Redirect to the intended page
88100
return redirect()->intended(route('dashboard', absolute: false));
89101
}
102+
103+
/**
104+
* Ensure the 2FA challenge is not rate limited.
105+
*
106+
* @param \App\Models\User $user
107+
* @return void
108+
*
109+
* @throws \Illuminate\Validation\ValidationException
110+
*/
111+
protected function ensureIsNotRateLimited(User $user): void
112+
{
113+
if (! RateLimiter::tooManyAttempts($this->throttleKey($user), 5)) {
114+
return;
115+
}
116+
117+
$seconds = RateLimiter::availableIn($this->throttleKey($user));
118+
119+
throw ValidationException::withMessages([
120+
'code' => __('Too many two factor authentication attempts. Please try again in :seconds seconds.', [
121+
'seconds' => $seconds,
122+
]),
123+
]);
124+
}
125+
126+
/**
127+
* Get the rate limiting throttle key for the given user.
128+
*
129+
* @param \App\Models\User $user
130+
* @return string
131+
*/
132+
protected function throttleKey(User $user): string
133+
{
134+
return Str::transliterate($user->id . '|2fa|' . request()->ip());
135+
}
90136
}
91137

0 commit comments

Comments
 (0)