Ask for the password over the page instead of throwing the form away

password.confirm redirected every write to the confirm-password screen.
A redirect cannot carry a POST body, and Redirector::guest() only
remembers the exact URL of a GET, so after confirming, the user landed
back on an empty form and the action never ran. On the API token forms
that meant typing the name, the scopes and the expiry again.

An Inertia request now gets a 423 marked X-Password-Confirmation. A
dialog mounted around every page catches it, asks for the password over
the current page, and sends the refused request again with the same data
and callbacks, so the form finishes as if nothing happened. The check
itself is still the framework's. Plain form posts and JSON clients are
answered as before, and accounts with no local password are offered a
way to set one, as the confirm screen does.
This commit is contained in:
ignacionelson
2026-09-21 18:05:54 -03:00
parent 60171799e7
commit a45eae315c
8 changed files with 352 additions and 4 deletions
@@ -7,6 +7,7 @@ use App\Modules\Identity\AuthSource;
use App\Modules\Identity\PasswordVerification;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response as HttpResponse;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
@@ -41,8 +42,12 @@ class ConfirmablePasswordController extends Controller
* their local hash is a Str::password(64) nobody has ever seen -- and
* this screen stands in front of enrolling in two-factor, so those
* accounts could not enrol at all.
*
* Asked for JSON, it answers with a bare 204: that is the password
* dialog (RequirePasswordConfirmation), which stays on the page and
* sends the refused request again itself, so there is nowhere to go.
*/
public function store(Request $request, PasswordVerification $passwords): RedirectResponse
public function store(Request $request, PasswordVerification $passwords): RedirectResponse|HttpResponse
{
$user = $request->user();
assert($user !== null);
@@ -55,6 +60,10 @@ class ConfirmablePasswordController extends Controller
$request->session()->put('auth.password_confirmed_at', time());
if ($request->expectsJson()) {
return response()->noContent();
}
return redirect()->intended(route('dashboard', absolute: false));
}
}