ensureFeatureIsEnabled($request); /** @var User $user */ $user = $request->user(); if ($user->hasEnabledTwoFactorAuthentication()) { return $this->enrollmentRedirect($request); } $this->validatePasswordForPasswordUser($request, $user); $enable($user, force: true); return $this->enrollmentRedirect($request) ->with('success', 'Scan the QR code, then enter a code to finish enabling two-factor authentication.'); } /** * Confirm a newly configured authenticator. */ public function confirm( Request $request, ConfirmTwoFactorAuthentication $confirm, SecurityEventRecorder $securityEvents ): RedirectResponse { $this->ensureFeatureIsEnabled($request); $validated = $request->validate([ 'code' => ['required', 'string', 'regex:/^\d{6}$/'], ]); /** @var User $user */ $user = $request->user(); $confirm($user, $validated['code']); $user->notify(new TwoFactorSecurityNotification( 'Two-factor authentication was enabled for your account.', (string) $request->ip(), now()->toIso8601String() )); $securityEvents->record('two_factor.enabled', $user, $request); $redirect = $request->routeIs('two-factor.setup.confirm') ? redirect()->route('two-factor.setup') : redirect()->route('profile.show'); return $redirect ->with('success', 'Two-factor authentication is now enabled.') ->with('recoveryCodes', $user->fresh()->recoveryCodes()); } /** * Disable two-factor authentication. */ public function disable( Request $request, DisableTwoFactorAuthentication $disable, SecurityEventRecorder $securityEvents ): RedirectResponse { $this->ensureFeatureIsEnabled($request); abort_if( config('auth-ui.features.two_factor_required'), 403, 'Two-factor authentication is required for this application.' ); /** @var User $user */ $user = $request->user(); if ($user->hasEnabledTwoFactorAuthentication()) { $this->validateSensitiveAction($request, $user); } $disable($user); $user->notify(new TwoFactorSecurityNotification( 'Two-factor authentication was disabled for your account.', (string) $request->ip(), now()->toIso8601String() )); $securityEvents->record('two_factor.disabled', $user, $request); return redirect()->route('profile.show') ->with('success', 'Two-factor authentication has been disabled.'); } /** * Reveal or regenerate recovery codes after re-authentication. */ public function recoveryCodes( Request $request, GenerateNewRecoveryCodes $generate, SecurityEventRecorder $securityEvents ): RedirectResponse { $this->ensureFeatureIsEnabled($request); /** @var User $user */ $user = $request->user(); if (! $user->hasEnabledTwoFactorAuthentication()) { abort(409, 'Two-factor authentication is not enabled.'); } $this->validateSensitiveAction($request, $user); if ($request->boolean('regenerate')) { $generate($user); $user->refresh(); $user->notify(new TwoFactorSecurityNotification( 'New two-factor authentication recovery codes were generated for your account.', (string) $request->ip(), now()->toIso8601String() )); $securityEvents->record('two_factor.recovery_codes_regenerated', $user, $request); } else { $securityEvents->record('two_factor.recovery_codes_revealed', $user, $request); } return redirect()->route('profile.show') ->with('success', $request->boolean('regenerate') ? 'New recovery codes have been generated. Previous codes no longer work.' : 'Recovery codes revealed.') ->with('recoveryCodes', $user->recoveryCodes()); } private function ensureFeatureIsEnabled(Request $request): void { abort_unless(config('auth-ui.features.two_factor'), 404); if ($request->routeIs('two-factor.setup.*')) { abort_unless(config('auth-ui.features.two_factor_required'), 404); } } private function enrollmentRedirect(Request $request): RedirectResponse { return $request->routeIs('two-factor.setup.*') ? redirect()->route('two-factor.setup') : redirect()->route('profile.show'); } private function validatePasswordForPasswordUser(Request $request, User $user): void { if (! $user->hasPassword()) { return; } $request->validate([ 'password' => ['required', 'string', 'current_password:web'], ]); } private function validateSensitiveAction(Request $request, User $user): void { if ($user->hasPassword()) { $this->validatePasswordForPasswordUser($request, $user); } $validated = $request->validate([ 'code' => ['required', 'string'], ]); if (! $this->hasValidTwoFactorCredential($user, $validated['code'])) { throw ValidationException::withMessages([ 'code' => 'The provided authentication or recovery code was invalid.', ]); } } private function hasValidTwoFactorCredential(User $user, string $code): bool { $secret = Fortify::currentEncrypter()->decrypt($user->two_factor_secret); if (app(TwoFactorAuthenticationProvider::class)->verify($secret, $code)) { return true; } $recoveryCode = collect($user->recoveryCodes()) ->first(fn (string $recoveryCode) => hash_equals($recoveryCode, $code)); if (! $recoveryCode) { return false; } $user->replaceRecoveryCode($recoveryCode); return true; } }