group(function () { Route::get('register', [RegistrationController::class, 'create']) ->name('register'); Route::post('register', [RegistrationController::class, 'store']) ->middleware('throttle:6,1,register'); Route::get('login', [AuthenticatedSessionController::class, 'create']) ->name('login'); Route::post('login', [AuthenticatedSessionController::class, 'store']); // Beginning a provider exchange is a guest action; completing one is // not necessarily — see the callback below, which sits outside every // group. Route::get('auth/{provider}/redirect', [SocialLoginController::class, 'redirect']) ->middleware('throttle:20,1,social-redirect') ->name('social.redirect'); Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) ->name('password.request'); // The broker's own throttle is per-address (config/auth.php), which // does nothing to stop one host walking a list of addresses — so the // endpoint is throttled per IP as well, same as register/2FA below. Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) ->middleware('throttle:6,1,password-email') ->name('password.email'); Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) ->name('password.reset'); Route::post('reset-password', [NewPasswordController::class, 'store']) ->middleware('throttle:6,1,password-reset') ->name('password.store'); Route::get('two-factor-challenge', [TwoFactorChallengeController::class, 'create']) ->name('two-factor.challenge'); Route::post('two-factor-challenge', [TwoFactorChallengeController::class, 'store']) ->middleware('throttle:6,1,two-factor'); }); // Deliberately in neither group. Signing in through a provider must not // require a session, and connecting one to an existing account requires // exactly that — so the guard is the intent written into the session // before the redirect, which also refuses a callback nobody asked for. Route::get('auth/{provider}/callback', [SocialLoginController::class, 'callback']) ->middleware('throttle:20,1,social-callback') ->name('social.callback'); Route::middleware('auth')->group(function () { Route::get('verify-email', EmailVerificationPromptController::class) ->name('verification.notice'); Route::get('verify-email/{id}/{hash}', VerifyEmailController::class) ->middleware(['signed', 'throttle:6,1,verify-email']) ->name('verification.verify'); Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) ->middleware('throttle:6,1,verification-send') ->name('verification.send'); Route::get('confirm-password', [ConfirmablePasswordController::class, 'show']) ->name('password.confirm'); // Named so EnforceTwoFactor can exempt it. Its exemption list matches // on route names, and an unnamed route matches nothing -- which left // the form reachable and its submission not, closing the enrolment // path enforcement depends on. // // Throttled because it checks a password. It was the one credential // check in this file with no bucket at all: not the per-email-and-IP // limiter POST login has, not a named `throttle:` like the rest -- // nothing, so an attacker holding a stolen session could sit on it // and guess. That is the wrong door to leave open, because passing it // is exactly what re-proving the password is meant to make expensive: // beyond it lie disabling two-factor, regenerating recovery codes and // minting an API token, and the password is then known for everything // else too. Six a minute, matching the other credential-facing // buckets here. Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']) ->middleware('throttle:6,1,password-confirm') ->name('password.confirm.store'); Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) ->name('logout'); });