laravel / fortify

Backend controllers and scaffolding for Laravel authentication.
https://laravel.com/docs/fortify
MIT License
1.58k stars 289 forks source link

Internal server error for forgot-password route if views is set to false in fortify config #155

Closed debarunmukherjee closed 3 years ago

debarunmukherjee commented 3 years ago

Description:

On disabling the view routes for fortify by setting 'views' => false in config/fortify.php, a POST request to the forgot-password route (via API from my front-end SPA) gives an internal server error, saying Route [password.reset] not defined. Here is a snapshot

image

The reason for this error is I believe, when sending the password reset link via email, we are trying get the route by the name password.reset. This route in created in the routes/routes.php file at this line. When the views config is set to false, this route is never getting created.

To work around this issue, I had to manually define a route in the standard laravel web.php file with this name and set it's view to the appropriate blade template.

Steps To Reproduce:

rodrigopedra commented 3 years ago

Fortify defers to the PasswordBroker implementation shipped with Laravel core for notifying the user of a password reset. The default ResetPassword notification shipped with Laravel, expects, without further customization, a route named password.reset to build the notification email.

The need for a route named password.reset is hinted on Laravel docs:

Next, we will define the routes necessary to actually reset the password...

https://laravel.com/docs/8.x/passwords#the-password-reset-form

Note that this route doesn't need to be defined on the ./routes/web.php file, use the web middleware stack, nor return a view. It can be defined on the ./routes/api.php file if you prefer so. You can even delegate to your SPA to handle the password reset flow.

The easiest "fix" for your use case is to add a route named password.reset and handle it in the way you think it is better.

I would say this is not a bug in Fortify, as the named route is required by a notification provided by Laravel's core. I'd rather say it is a "light requirement" of the framework. "Light requirement" as it can be customized further, and totally avoided, if needed or wanted.

If you don't want to have a route named password.reset, for whatever reason, you can customize how the ResetPassword notification is built or sent.

You can either override the sendPasswordResetNotification method on your app's local User model and send a custom notification. Or use the Illuminate\Auth\Notifications\ResetPassword static callbacks to either replace the notification MailMessage altogether or provide a custom URL for the reset password action button in the default email body.

Please, refer to the Illuminate\Auth\Notifications\ResetPassword source to see how to use these static callbacks:

https://github.com/laravel/framework/blob/43bea00fd27c76c01fd009e46725a54885f4d2a5/src/Illuminate/Auth/Notifications/ResetPassword.php#L60-L81

Hope it helps.

driesvints commented 3 years ago

@rodrigopedra thanks for the thorough reply here!

rodrigopedra commented 3 years ago

Thank you @driesvints !

vollyimnetz commented 3 years ago

@rodrigopedra, wow, thanks for the in depth explanation.

I stumbled about the error message today. I have created my own vue SPA that rebuilds the functionality of the PHP-Views.

For documentation, and anyone that needs some help, here is my setup:

in config/fortify.php views' => false, --> to disable the php (GET) views

in your vue component for forgot-password Send POST data to "/forgot-password" (use form field: email)

in your vue component for reset-password This vue component is accessable via vue-router on "/base/reset-password/:token". Send POST data to "/reset-password" (use form fields: token, email, password, password_confirmation)

in routes/web.php

Route::get('/base/reset-password/{token}', function () { 
    return view('app');//the view for the spa base-html
})->name('password.reset');//laravel needs a route with this name for password reset

Fortify will get the correct view for the e-mail by reading the route names. So i defined a Route with the path "/base/reset-password/:token" and fortify will use this URL in the password-reset-email, cause its named "password.reset". (Note: I used "base" path for every aspect of my vue SPA that is related to the laravel base behaiviour).

driesvints commented 3 years ago

I tried documenting this here: https://github.com/laravel/fortify/pull/157

y0rdie commented 3 years ago

@vollyimnetz FWIW I have a similar project called Laravel Gust that provides a scaffold for a Vue.js SPA which supports Fortify, Breeze or UI (if you're that way inclined). Feel free to take a look at the repo to see what changes are required on the backend for each of these auth packages in order to fully support a SPA.

davidwbsc commented 2 months ago

Fortify defers to the PasswordBroker implementation shipped with Laravel core for notifying the user of a password reset. The default ResetPassword notification shipped with Laravel, expects, without further customization, a route named password.reset to build the notification email.

Is this still the case in Fortify with Laravel 11? I can't see anything in the docs that indicate that there is any further set up to get this to work using an API:

If the password reset link request was successful, Fortify will redirect the user back to the /forgot-password endpoint and send an email to the user with a secure link they can use to reset their password. If the request was an XHR request, a 200 HTTP response will be returned.

I have set up Laravel 11 with Fortify and Sanctum and I can log in fine, but I get a 500 when I try and reset password with Route [password.reset] not defined.