Certainly! Here's a detailed solution for your Laracasts question, explaining both the cause and the way to allow email verification even if the user is logged out or using a different browser.
Problem Explanation
By default, Laravel's email verification requires the user to be authenticated (logged in) to verify their email. This is enforced by the auth middleware on the route:
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
->middleware(['auth', 'signed', 'throttle:6,1'])
->name('verification.verify');
If the user is not logged in, visiting the verification link simply redirects to the login page, and email_verified_at won't get updated.
This behavior is a security choice, but often users expect to be able to verify from a new device, browser, or after logging out.
Solution: Remove the auth Middleware
To allow verification without requiring the user to be logged in, remove the auth middleware from your verification route, keeping the signed middleware for security. The signed middleware ensures the link can’t be tampered with.
Change your route in routes/web.php or routes/auth.php to:
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
->middleware(['signed', 'throttle:6,1']) // removed 'auth'
->name('verification.verify');
But Wait—Now What about Verification Logic?
By default, your VerifyEmailController may reference auth()->user(). If the user is not logged in, auth()->user() will be null. Instead, you can retrieve the user by the ID in the route, and manually mark their email as verified.
Here's a simple custom controller as an example:
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Support\Facades\Auth;
public function __invoke(Request $request, $id, $hash)
{
$user = User::findOrFail($id);
if (! hash_equals((string) $hash, sha1($user->getEmailForVerification()))) {
abort(403, 'Invalid verification link');
}
if (! $user->hasVerifiedEmail()) {
$user->markEmailAsVerified();
event(new Verified($user));
}
// Optionally, log in the user or redirect with message.
// Auth::login($user); // Uncomment to log them in automatically
return redirect('/login')->with('status', 'Your email has been verified! You can now login.');
}
This is similar to how Laravel does it under the hood, except it does not require the logged-in session.
Final Tips
- Resend Verification: Users will still need to log in to request another verification email, since that route will usually require authentication.
- Security: Keeping
signed in the middleware is crucial, as it ensures the link can't be manipulated.
- Customization: You can automatically log the user in upon verification, or just redirect with a success message.
Summary
- Remove
'auth' from the verification route middleware.
- In your controller, fetch the user by ID, check the hash, and mark as verified.
- Keep
signed and throttle middleware for security.
Example Route and Controller
// In routes/web.php or auth.php
Route::get('verify-email/{id}/{hash}', 'App\Http\Controllers\Auth\CustomVerifyEmailController')
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
// In app/Http/Controllers/Auth/CustomVerifyEmailController.php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Auth\Events\Verified;
class CustomVerifyEmailController extends Controller
{
public function __invoke(Request $request, $id, $hash)
{
$user = User::findOrFail($id);
if (! hash_equals((string) $hash, sha1($user->getEmailForVerification()))) {
abort(403, 'Invalid verification link');
}
if (! $user->hasVerifiedEmail()) {
$user->markEmailAsVerified();
event(new Verified($user));
}
// Optionally, log the user in:
// Auth::login($user);
return redirect('/login')->with('status', 'Your email has been verified! Please log in.');
}
}
This will let users verify their email from any browser, even if they are not logged in.
Let me know if you need a ready-to-use controller stub or any more help!