Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Reauthenticate Laravel Package

mpociot/reauthenticate

Laravel package for reauthentication: middleware and traits that require users to re-enter their password before sensitive actions such as changing an email or deleting an account, with a built-in confirmation view and configurable timeout.

View on GitHub
Deep Wiki
Context7

Getting Started

Check the version fit before you type composer require. If you're on Laravel 6.2+, stop here: Illuminate\Auth\Middleware\RequirePassword plus the password.confirmation route (Fortify, Breeze, or laravel/ui) is the same GitHub-style sudo UX with zero third-party surface, actively maintained, and tested against your PHP version. This package's last release was 2019-10-08 and it exists to fill a gap that closed a long time ago. Adopt it only if you're pinned to Laravel 5.x and an upgrade is genuinely out of scope, or if you want it as a readable MIT reference to copy from rather than depend on.

Install and publish.

composer require mpociot/reauthenticate
php artisan vendor:publish --provider="Mpociot\Reauthenticate\ReauthenticateServiceProvider"

Pin the version explicitly in composer.json. Verify the provider FQCN against src/ — the publish flag fails silently-ish with a confusing error if the namespace has drifted. Confirm what landed: config/reauthenticate.php, a Blade view, and (probably) a route for the confirmation form.

Read the middleware line by line before writing a single route. src/Reauthenticate.php is short and it is the whole contract. You need three facts out of it:

  1. The exact session key it sets on success — that key is your integration point, your test assertion, and your migration path.
  2. The timeout unit and the comparison operator (>= vs >, absolute timestamp vs. duration). Neither is documented.
  3. Which guard it resolves the user from, and which provider it validates the password against.

Write those three down in your own words in the PR description. Future-you will not re-derive them.

Register your own alias, not the vendor's, then protect one route.

// app/Http/Kernel.php
protected $routeMiddleware = [
    // ...
    'sudo' => \App\Http\Middleware\Sudo::class, // your class, see below
];

First use case: the profile/email edit page. It exercises the full loop — authenticated request → redirect to the confirmation form → password POST → redirect back to the originally intended URL — with zero blast radius. Walk it manually once, then protect password change, then API keys, then billing, then account deletion. One route at a time, verifying each.

Smoke-test checklist before you call it done:

  • php artisan route:list | grep -i reauth — note the route name, URI, and method. The redirect target depends on all three.
  • Protected route while unconfirmed → 302 to the form, intended URL stashed in session.
  • Confirmed → reaches the controller.
  • dd(session()->all()) immediately before and after the confirmation POST. See the key appear.

Implementation Patterns

Keep the sensitive-route registry in one reviewable place. Sudo mode earns its keep when a security reviewer can look at a single block and see every protected surface:

Route::middleware(['auth', 'sudo'])->group(function () {
    Route::get('/settings/profile',  [ProfileController::class, 'edit']);
    Route::put('/settings/password', [PasswordController::class, 'update']);
    Route::get('/settings/api-keys', [ApiKeyController::class, 'index']);
    Route::delete('/account',        [AccountController::class, 'destroy']);
});

Twenty scattered ->middleware('sudo') calls means nobody can audit what's protected when the sensitive-action list changes.

Wrap the vendor middleware behind your own class. Call sites reference sudo, never the vendor alias. That makes the eventual migration to password.confirm, a fork, or a homegrown implementation a one-line change in Kernel.php:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class Sudo
{
    public function handle(Request $request, Closure $next)
    {
        // Non-HTML clients get a machine-readable signal, not a redirect
        // into a Blade form they cannot render.
        if ($request->expectsJson() || $request->header('X-Inertia')) {
            if (! $this->isReauthenticated($request)) {
                return response()->json(
                    ['message' => 'Password confirmation required'],
                    423 // Locked
                );
            }
            return $next($request);
        }

        return app(\Mpociot\Reauthenticate\Reauthenticate::class)
            ->handle($request, $next);
    }

    private function isReauthenticated(Request $request): bool
    {
        // Read the key name out of the published config / middleware source.
        // Do not guess it.
        return $request->session()->has(config('reauthenticate.session_key'));
    }
}

Duplicating the guard condition is safer than probing the vendor class with reflection. Keep the duplicated logic to one method and comment it with the source you copied it from.

Handle Livewire, Inertia, and XHR deliberately. The package's failure response is an HTTP redirect to an HTML form. Any of those clients will follow it into a document they can't render, and you'll spend day two debugging "why is my fetch returning HTML." Return 423 Locked from your wrapper and have the frontend open a modal that POSTs to your confirmation route; on success, retry the original request.

Align the timeout with the action, not globally. The package reads one timeout from config. If impersonation should be 5 minutes and billing 60, you have three options: accept one window and document it prominently, put two thin subclasses in app/Http/Middleware/ that set different config values before delegating, or fork. A per-action policy engine is out of scope and always will be — don't pretend otherwise in the ticket.

Write the feature-test matrix before you touch production routes. These tests are both your safety net and your exit characterization suite for the eventual replacement:

// authenticated + confirmed within window  -> 200
// authenticated + stale                    -> 302 to confirmation route
//                                           + intended URL in session
// correct password POST                    -> 302 to intended URL, key set
// wrong password POST                      -> back to form, errors, no key
// $this->travel($timeout)->minutes()       -> prompts again
// $this->travel($timeout - 1)->minutes()   -> passes

Run every case against each guard (web, admin, customer) separately. Every one of these becomes a migration test later, for free.

Log the confirmation somewhere. The package fires no event on success. If you need an audit trail — and for any security narrative you do — override the confirmation controller in app/Http/Controllers/, or observe the session-key transition inside your wrapper and write to your audit log there. Keep the override in app/ so a fork inherits it.

Plan the exit while you install. Preserve the session key's semantics (or translate once, on deploy) so users mid-window aren't bounced to a re-prompt. Keep the sudo alias name stable so no route file changes during the swap. On Laravel 5.x, forking the few hundred lines into app/Http/Middleware/ and deleting the Composer entry is usually cheaper than carrying an unowned dependency — the code is MIT, small, and you had to read all of it anyway.

Gotchas and Tips

The maintenance gap is the top risk, not a footnote. Last release 2019-10-08. No CI matrix, no security disclosure process, zero listed dependents. On password-handling code, an unowned dependency is a finding in your next review, not an inconvenience. Either name a team owner and fork it, or don't take it.

Confirm the timeout unit and comparison operator from source, never from docs. Minutes vs. seconds, absolute timestamp vs. duration, >= vs. > at the boundary — none of it is documented and all of it matters. Your timeout / timeout - 1 tests are how you pin it down. If you fork, add a comment at the comparison line.

Multi-guard apps break quietly. The middleware likely resolves the default guard from config. With separate admin and customer guards, verify the password check runs against the guard the request actually authenticated on. A wrong-provider check either always fails (visible, annoying) or compares against the wrong user's hash (an incident). Test each guard individually using the same form.

Fail-open is the failure mode that turns a bug into a breach. A missing config key, an unresolvable guard, a typo'd route group — any of these can pass the request through. Add an assertion test that a protected route redirects an unconfirmed user. If a misconfiguration can quietly skip the check, wrap the middleware so it throws instead.

Passwordless, SSO, and passkey users have nothing to type. OAuth-only users hit a form they can never satisfy. Check $user->password and either skip them with a compensating control (fresh MFA challenge, recent-login assertion) or return a clear "contact your administrator" path. Do not discover this from a bug report from an enterprise SSO customer.

Redirect handling is an open-redirect surface. "Return to intended URL" reads a session key or query param. Validate it's a relative path or an allowlisted host, and test ?redirect=https://evil.example and ?redirect=//evil.example explicitly. Never pipe user input straight into redirect()->intended().

Don't run it alongside password.confirm on the same group. Two middlewares with different session keys, each redirecting to its own form, produces a loop the user can never exit. Migrate group by group, not in place.

PHP 8.1+ is untested upstream. Laravel 6 supported PHP 7.2–8.0, so the package was likely only ever exercised up to 8.0. Expect deprecation notices and possible null-to-non-nullable type errors. Run the full flow on your actual runtime; don't infer safety from composer.json constraints.

Multi-node without session affinity looks like a package bug. Redis or database sessions are fine. File sessions behind a load balancer that doesn't pin the user will reject a confirmation made on node A when the next request lands on node B. Symptom: an inexplicable re-auth prompt immediately after a successful one.

Route name and view collisions. The package registers a route name and publishes a Blade view. If your auth scaffolding publishes a view at the same path or registers the same route name, you get silent shadowing. Re-run php artisan route:list | grep -i reauth after every scaffolding upgrade.

Debugging shortcut: dd(session()->all()) immediately before and after the confirmation POST. Whether the key is absent, present, or present-with-an-unexpected-value tells you in one glance which of the three classic failures you have — middleware not hit at all, confirmation not persisting, or timeout comparison wrong. Almost every flow bug is one of those three.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin