Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

calvincani's avatar

Email verification not working

I followed the instructions on Laravel.com and I have the following routes in my web.php file:

Auth::routes(['verify' => true]);
Route::get('/email/verify', function () {
    return view('accounts.verify');
})->middleware('auth')->name('verification.notice');

Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
    $request->fulfill();
    return redirect('login');
})->middleware(['auth', 'signed'])->name('verification.verify');

Route::post('/email/verification-notification', function (Request $request) {
    $request->user()->sendEmailVerificationNotification();
    return back()->with('message', 'Verification link sent!');
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');

I get the email with the link and I can click on it and it directs me to the login page but if I look in the database the column email_verified_at shows NULL so the email is not verified. What am I doing wrong?

0 likes
13 replies
Rutherfordium's avatar

Have you implement MustVerifiyEmail in your User modal ?

1 like
calvincani's avatar

@Rutherfordium here is my user model file:

<?php

namespace App\Models;

use Laravel\Sanctum\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'role',
        'email',
        'password',
        'accepted'
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function profile($user)
    {
        if ($user->hasVerifiedEmail()) {
            if ($user->role == 'contestant') {
                return $this->hasOne(ContestantProfile::class, 'user_id');
            } else if ($user->role == 'voter') {
                return $this->hasOne(VoterProfile::class, 'user_id');
            }
        }
    }
}

```1
Rutherfordium's avatar

@calvincani I just try the verification method specified in the documentation and it actually correct that you are redirected to Login page, because in EmailVerificationRequest especially in fullfill method. You will see that there is$this->user() that resolve to the current user who are making the request.

And in this route bellow it applied auth middleware, if a user is not logged in it will automatically redirect to the Login route I presume

Route::post('/email/verification-notification', function (Request $request) {
    $request->user()->sendEmailVerificationNotification();
    return back()->with('message', 'Verification link sent!');
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');

#Edit I copied the wrong piece of code

Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
    $request->fulfill();
    return redirect('login');
})->middleware(['auth', 'signed'])->name('verification.verify');
calvincani's avatar

@Rutherfordium ok thank you but the problem is the user is not getting verified and the column in the database shows NULL so it's not working and the user's email address is not getting verified, which is the actual problem.

Rutherfordium's avatar

@calvincani Make sure you already login before you click that verification link, because once I register I automatically got logged in and once I click that verification link, it goes straight fill to fill my email_verified_at

You can try debugging in EmailVerificationRequest by using dd($this->user()->toArray()) in the fullfill method and see what happen

PS: You can try to crosscheck whether the link you click is the user's email which you are actually checking / want to verify

calvincani's avatar

@Rutherfordium I thought that is the problem because the route uses the middleware auth which is to verify if a user is logged in. I don't want users to be able to log in before they have confirmed their email. Because if you are able to login before verifying your email address is real then you are able to do things I don't want you to do. Then I have to do a bunch of extra work to block your login if you have not confirmed after some time and clean up accounts that's not confirmed where if they could not log in I can just ruin a query and delete all users not verified after 14 days or something. I will remove the middleware that checks if a user is signed in and I hope it works.

dondelion's avatar

Aloha @calvincani ! Same issue and usecase here. Do you have any updates?

Removing the middleware gives the error "Call to a member function getKey() on null" as Laravel requires the user to be logged in to be verified.

hahaha's avatar

@dondelion , if u want to only verify the email without making the user to login in, u need to override the verification model inside the verificationController like below and remove the use VerifyEmail inside the verificationcontroller, and also don't forget to remove the auth meddleware too.

in your web.php add this:

Route::get('/email/verify/{id}/{hash}', [VerificationController::class, 'verify'])
		->middleware(['signed'])
		->name('verification.verify');

in your verificationcontroller add this:

public function verify(Request $request)
{
    $user = User::findOrFail($request->id);

    if (!$user) {
        return to_route('v2.auth.signup')
        ->with('message', trans('auth.invalid_verification_link'));
    }

    if (!hash_equals((string) $request->hash, sha1($user->getEmailForVerification()))) {
        return to_route('v2.auth.signup')
        ->with('message', trans('auth.invalid_verification_link'));
    }

    if ($user->markEmailAsVerified()) {
        event(new Verified($user));
    }

    return redirect()->route('auth.login')->with('verified', true);
}
Snapey's avatar

@benshawuk you have to be logged in when you hit the verification route, or login and then go to the verification.

If you have altered the redirect logic in the authentication then maybe you have removed the intended route?

benshawuk's avatar

@Snapey Thanks for responding! I figured out my problem. In messing with the routes/web.php config, I had removed this key part that Jetstream requires for handling this (which gets automatically placed in when you install Jetstream):

Route::middleware([
    'auth:sanctum',
    config('jetstream.auth_session'),
    'verified'
])->group(function () // ...... <routes go here>

Though in my case I actually had to add this to FilamentPHP's middleware config to get it working in my Filament app:

'middleware' => [
        'auth' => [
            Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
            Authenticate::class,
        ],```
rubydigitalprojex's avatar

@benshawuk I have Filament, but I can't get the /email/verify/{id}/{hash} path to work. How did you add the route to make it work for you?

Jarhakoz's avatar

Also just figured out, that the verification link email/verify/{id}/{hash} does not work if user is not signed in! It's using session hash? Link must be opened in same browser to work. The (non-verified) user must be allowed to sign in, at least minimum to the Jetstream (user profile) routes should be outside of verified-middleware. You can place all your program routes to use verified-middleware, but not the user profile. If user can not login, they can not re-request the verification link either. So allow the non-verified to login, to minimal access to profile, and place all other routes behind verified-middleware.

Please or to participate in this conversation.