From edb0b540aaeff311a17e131bdd13baabbefe46a6 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 13 Oct 2025 12:26:39 +0530 Subject: [PATCH 1/3] Replace Login and Logout by replacing it with fortify --- .../Auth/AuthenticatedSessionController.php | 63 ------------- app/Http/Requests/Auth/LoginRequest.php | 94 ------------------- app/Providers/FortifyServiceProvider.php | 29 ++++++ resources/js/pages/auth/login.tsx | 4 +- routes/auth.php | 10 -- tests/Feature/Auth/AuthenticationTest.php | 8 +- tests/Feature/Auth/TwoFactorChallengeTest.php | 10 +- 7 files changed, 35 insertions(+), 183 deletions(-) delete mode 100644 app/Http/Controllers/Auth/AuthenticatedSessionController.php delete mode 100644 app/Http/Requests/Auth/LoginRequest.php diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php deleted file mode 100644 index 80da6826b..000000000 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ /dev/null @@ -1,63 +0,0 @@ - Route::has('password.request'), - 'status' => $request->session()->get('status'), - ]); - } - - /** - * Handle an incoming authentication request. - */ - public function store(LoginRequest $request): RedirectResponse - { - $user = $request->validateCredentials(); - - if (Features::enabled(Features::twoFactorAuthentication()) && $user->hasEnabledTwoFactorAuthentication()) { - $request->session()->put([ - 'login.id' => $user->getKey(), - 'login.remember' => $request->boolean('remember'), - ]); - - return to_route('two-factor.login'); - } - - Auth::login($user, $request->boolean('remember')); - - $request->session()->regenerate(); - - return redirect()->intended(route('dashboard', absolute: false)); - } - - /** - * Destroy an authenticated session. - */ - public function destroy(Request $request): RedirectResponse - { - Auth::guard('web')->logout(); - - $request->session()->invalidate(); - $request->session()->regenerateToken(); - - return redirect('/'); - } -} diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php deleted file mode 100644 index d426f112c..000000000 --- a/app/Http/Requests/Auth/LoginRequest.php +++ /dev/null @@ -1,94 +0,0 @@ -|string> - */ - public function rules(): array - { - return [ - 'email' => ['required', 'string', 'email'], - 'password' => ['required', 'string'], - ]; - } - - /** - * Validate the request's credentials and return the user without logging them in. - * - * @throws \Illuminate\Validation\ValidationException - */ - public function validateCredentials(): User - { - $this->ensureIsNotRateLimited(); - - /** @var User|null $user */ - $user = Auth::getProvider()->retrieveByCredentials($this->only('email', 'password')); - - if (! $user || ! Auth::getProvider()->validateCredentials($user, $this->only('password'))) { - RateLimiter::hit($this->throttleKey()); - - throw ValidationException::withMessages([ - 'email' => __('auth.failed'), - ]); - } - - RateLimiter::clear($this->throttleKey()); - - return $user; - } - - /** - * Ensure the login request is not rate limited. - * - * @throws \Illuminate\Validation\ValidationException - */ - public function ensureIsNotRateLimited(): void - { - if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { - return; - } - - event(new Lockout($this)); - - $seconds = RateLimiter::availableIn($this->throttleKey()); - - throw ValidationException::withMessages([ - 'email' => __('auth.throttle', [ - 'seconds' => $seconds, - 'minutes' => ceil($seconds / 60), - ]), - ]); - } - - /** - * Get the rate-limiting throttle key for the request. - */ - public function throttleKey(): string - { - return $this->string('email') - ->lower() - ->append('|'.$this->ip()) - ->transliterate() - ->value(); - } -} diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index c13f6ee17..56fface82 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -6,7 +6,9 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\ServiceProvider; +use Illuminate\Support\Str; use Inertia\Inertia; +use Laravel\Fortify\Features; use Laravel\Fortify\Fortify; class FortifyServiceProvider extends ServiceProvider @@ -24,11 +26,38 @@ public function register(): void */ public function boot(): void { + $this->configureViews(); + $this->configureRateLimiting(); + } + + /** + * Configure Fortify views. + */ + private function configureViews(): void + { + Fortify::loginView(fn (Request $request) => Inertia::render('auth/login', [ + 'canResetPassword' => Features::enabled(Features::resetPasswords()), + 'status' => $request->session()->get('status'), + ])); + Fortify::twoFactorChallengeView(fn () => Inertia::render('auth/two-factor-challenge')); + Fortify::confirmPasswordView(fn () => Inertia::render('auth/confirm-password')); + } + /** + * Configure rate limiting. + */ + private function configureRateLimiting(): void + { RateLimiter::for('two-factor', function (Request $request) { return Limit::perMinute(5)->by($request->session()->get('login.id')); }); + + RateLimiter::for('login', function (Request $request) { + $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip()); + + return Limit::perMinute(5)->by($throttleKey); + }); } } diff --git a/resources/js/pages/auth/login.tsx b/resources/js/pages/auth/login.tsx index d52765bd4..7fff5f54c 100644 --- a/resources/js/pages/auth/login.tsx +++ b/resources/js/pages/auth/login.tsx @@ -1,4 +1,3 @@ -import AuthenticatedSessionController from '@/actions/App/Http/Controllers/Auth/AuthenticatedSessionController'; import InputError from '@/components/input-error'; import TextLink from '@/components/text-link'; import { Button } from '@/components/ui/button'; @@ -7,6 +6,7 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import AuthLayout from '@/layouts/auth-layout'; import { register } from '@/routes'; +import { store } from '@/routes/login'; import { request } from '@/routes/password'; import { Form, Head } from '@inertiajs/react'; import { LoaderCircle } from 'lucide-react'; @@ -25,7 +25,7 @@ export default function Login({ status, canResetPassword }: LoginProps) {
diff --git a/routes/auth.php b/routes/auth.php index 191847eba..8ae5b957a 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,6 +1,5 @@ name('register.store'); - Route::get('login', [AuthenticatedSessionController::class, 'create']) - ->name('login'); - - Route::post('login', [AuthenticatedSessionController::class, 'store']) - ->name('login.store'); - Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) ->name('password.request'); @@ -46,7 +39,4 @@ Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) ->middleware('throttle:6,1') ->name('verification.send'); - - Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) - ->name('logout'); }); diff --git a/tests/Feature/Auth/AuthenticationTest.php b/tests/Feature/Auth/AuthenticationTest.php index 54b3ce13c..a169a340c 100644 --- a/tests/Feature/Auth/AuthenticationTest.php +++ b/tests/Feature/Auth/AuthenticationTest.php @@ -87,17 +87,13 @@ public function test_users_are_rate_limited() { $user = User::factory()->create(); - RateLimiter::increment(implode('|', [$user->email, '127.0.0.1']), amount: 10); + RateLimiter::increment(md5('login'.implode('|', [$user->email, '127.0.0.1'])), amount: 5); $response = $this->post(route('login.store'), [ 'email' => $user->email, 'password' => 'wrong-password', ]); - $response->assertSessionHasErrors('email'); - - $errors = session('errors'); - - $this->assertStringContainsString('Too many login attempts', $errors->first('email')); + $response->assertTooManyRequests(); } } diff --git a/tests/Feature/Auth/TwoFactorChallengeTest.php b/tests/Feature/Auth/TwoFactorChallengeTest.php index b8d6b803e..b14f5fe5c 100644 --- a/tests/Feature/Auth/TwoFactorChallengeTest.php +++ b/tests/Feature/Auth/TwoFactorChallengeTest.php @@ -4,7 +4,7 @@ use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; -use Inertia\Testing\AssertableInertia as Assert; +use Inertia\Testing\AssertableInertia; use Laravel\Fortify\Features; use Tests\TestCase; @@ -36,12 +36,6 @@ public function test_two_factor_challenge_can_be_rendered(): void $user = User::factory()->create(); - $user->forceFill([ - 'two_factor_secret' => encrypt('test-secret'), - 'two_factor_recovery_codes' => encrypt(json_encode(['code1', 'code2'])), - 'two_factor_confirmed_at' => now(), - ])->save(); - $this->post(route('login'), [ 'email' => $user->email, 'password' => 'password', @@ -49,7 +43,7 @@ public function test_two_factor_challenge_can_be_rendered(): void $this->get(route('two-factor.login')) ->assertOk() - ->assertInertia(fn (Assert $page) => $page + ->assertInertia(fn (AssertableInertia $page) => $page ->component('auth/two-factor-challenge') ); } From 598adc81cdd29a6ed3b36f5c305450284d5cb156 Mon Sep 17 00:00:00 2001 From: Pushpak Chhajed Date: Mon, 13 Oct 2025 16:17:59 +0530 Subject: [PATCH 2/3] Replace email verification with fortify --- ...mailVerificationNotificationController.php | 24 ------------------- .../EmailVerificationPromptController.php | 22 ----------------- .../Auth/VerifyEmailController.php | 24 ------------------- app/Providers/FortifyServiceProvider.php | 4 ++++ config/fortify.php | 2 +- resources/js/pages/auth/verify-email.tsx | 14 ++++------- routes/auth.php | 16 ------------- 7 files changed, 10 insertions(+), 96 deletions(-) delete mode 100644 app/Http/Controllers/Auth/EmailVerificationNotificationController.php delete mode 100644 app/Http/Controllers/Auth/EmailVerificationPromptController.php delete mode 100644 app/Http/Controllers/Auth/VerifyEmailController.php diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php deleted file mode 100644 index f64fa9ba7..000000000 --- a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php +++ /dev/null @@ -1,24 +0,0 @@ -user()->hasVerifiedEmail()) { - return redirect()->intended(route('dashboard', absolute: false)); - } - - $request->user()->sendEmailVerificationNotification(); - - return back()->with('status', 'verification-link-sent'); - } -} diff --git a/app/Http/Controllers/Auth/EmailVerificationPromptController.php b/app/Http/Controllers/Auth/EmailVerificationPromptController.php deleted file mode 100644 index 672f7cfce..000000000 --- a/app/Http/Controllers/Auth/EmailVerificationPromptController.php +++ /dev/null @@ -1,22 +0,0 @@ -user()->hasVerifiedEmail() - ? redirect()->intended(route('dashboard', absolute: false)) - : Inertia::render('auth/verify-email', ['status' => $request->session()->get('status')]); - } -} diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php deleted file mode 100644 index db389f20d..000000000 --- a/app/Http/Controllers/Auth/VerifyEmailController.php +++ /dev/null @@ -1,24 +0,0 @@ -user()->hasVerifiedEmail()) { - return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); - } - - $request->fulfill(); - - return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); - } -} diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 56fface82..e8fc91508 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -40,6 +40,10 @@ private function configureViews(): void 'status' => $request->session()->get('status'), ])); + Fortify::verifyEmailView(fn (Request $request) => Inertia::render('auth/verify-email', [ + 'status' => $request->session()->get('status'), + ])); + Fortify::twoFactorChallengeView(fn () => Inertia::render('auth/two-factor-challenge')); Fortify::confirmPasswordView(fn () => Inertia::render('auth/confirm-password')); diff --git a/config/fortify.php b/config/fortify.php index df49e4f8c..0846d4498 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -146,7 +146,7 @@ 'features' => [ // Features::registration(), // Features::resetPasswords(), - // Features::emailVerification(), + Features::emailVerification(), // Features::updateProfileInformation(), // Features::updatePasswords(), Features::twoFactorAuthentication([ diff --git a/resources/js/pages/auth/verify-email.tsx b/resources/js/pages/auth/verify-email.tsx index 050c05563..43c708b0d 100644 --- a/resources/js/pages/auth/verify-email.tsx +++ b/resources/js/pages/auth/verify-email.tsx @@ -1,12 +1,11 @@ // Components -import EmailVerificationNotificationController from '@/actions/App/Http/Controllers/Auth/EmailVerificationNotificationController'; -import { logout } from '@/routes'; -import { Form, Head } from '@inertiajs/react'; -import { LoaderCircle } from 'lucide-react'; - import TextLink from '@/components/text-link'; import { Button } from '@/components/ui/button'; import AuthLayout from '@/layouts/auth-layout'; +import { logout } from '@/routes'; +import { send } from '@/routes/verification'; +import { Form, Head } from '@inertiajs/react'; +import { LoaderCircle } from 'lucide-react'; export default function VerifyEmail({ status }: { status?: string }) { return ( @@ -23,10 +22,7 @@ export default function VerifyEmail({ status }: { status?: string }) { )} - + {({ processing }) => ( <>