I had to develop exactly the same functionality for my rest laravel 8 api, I share my work with you, hoping to be able to help you.
To begin, your problem is that the user is redirected to the login page after clicking on the verification link. But the question is has the user been marked as verified in the database when he click ?
If it is marked as verified in the database after the click, the functionality is working but the problem is the redirection. Because if you are using a Rest API you would probably want the user to be redirected to a login or success page of your frontend application.
The last problem is your middleware. First in the api.php file the middleware for the connection is 'auth:api' instead of 'auth'. But for once you do not have to put middleware on the verification route otherwise you will have to have the user connect so that he validates his email and since you go through an API route it is pretty boring ...
Finally here is the solution I opted for :
1. In your app/Models/User.php implements MustVerifyEmail (Normally, from what I understood, that you already did, but I prefer to put it in case if other people go through this topic)
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
{
use HasFactory, Notifiable, HasApiTokens;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
2. In your app/Http/Controllers/AuthController.php add event on registered user (Normally, from what I understood, that you already did, but I prefer to put it in case if other people go through this topic)
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Registered;
class AuthController extends Controller
{
public function register(Request $request)
{
$validatedData = $request->validate([
'name' => 'required|max:55',
'email' => 'email|required|unique:users',
'password' => 'required|confirmed'
]);
$validatedData['password'] = bcrypt($request->password);
$user = User::create($validatedData);
event(new Registered($user));
$accessToken = $user->createToken('authToken')->accessToken;
return response(['user' => $user, 'access_token' => $accessToken]);
}
public function login(Request $request)
{
$loginData = $request->validate([
'email' => 'email|required',
'password' => 'required'
]);
if (!auth()->attempt($loginData)) {
return response(['message' => 'Invalid Credentials']);
}
$accessToken = auth()->user()->createToken('authToken')->accessToken;
return response(['user' => auth()->user(), 'access_token' => $accessToken]);
}
}
3. In your routes/api.php defines this routes :
// Verify email
Route::get('/email/verify/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
// Resend link to verify email
Route::post('/email/verify/resend', function (Request $request) {
$request->user()->sendEmailVerificationNotification();
return back()->with('message', 'Verification link sent!');
})->middleware(['auth:api', 'throttle:6,1'])->name('verification.send');
4. Create app/Http/Controllers/VerifyEmailController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use App\Models\User;
class VerifyEmailController extends Controller
{
public function __invoke(Request $request): RedirectResponse
{
$user = User::find($request->route('id'));
if ($user->hasVerifiedEmail()) {
return redirect(env('FRONT_URL') . '/email/verify/already-success');
}
if ($user->markEmailAsVerified()) {
event(new Verified($user));
}
return redirect(env('FRONT_URL') . '/email/verify/success');
}
}
Explanations:
With this solution we keep all the operation of checking the official documentation by email. Except that instead of checking if the user is connected to retrieve it and put his email in verified. We launch a method in a controller which will find the corresponding user to put it in verified.
I hope I was understandable and that it can help you :)