I’m building an API using Laravel 10 with Sanctum for authentication, and I’m encountering an issue with email verification. After the user clicks the verification link, the email_verified_at field remains null.
This is my register component
import React from "react";
import { useNavigate } from "react-router-dom";
function Register() {
const [name, setName] = React.useState("");
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const navigate = useNavigate();
React.useEffect(() => {
if (localStorage.getItem("token")) {
navigate("/account");
window.location.reload();
}
}, []);
async function register() {
const item = { name, email, password };
let result = await fetch("http://localhost:8000/api/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(item),
});
const data = await result.json();
if (result.ok) {
alert("Please verify your email before logging in.");
navigate("/login");
} else {
alert(data.message || "Error registering.");
}
}
return (
<div className="py-16">
<div className="flex items-center flex-col justify-center gap-6">
<div className="flex items-center justify-center">
<h1>Register Form</h1>
</div>
<div className="flex items-center flex-col gap-5 justify-center">
<input
type="text"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div>
<button onClick={register} className="border-2 border-black p-2">
Register
</button>
</div>
</div>
</div>
);
}
export default Register;
This is my auth controller
<?php
namespace AppHttpControllers;
use IlluminateValidationValidationException; //manually imported
use IlluminateSupportFacadesHash; //manually imported
use IlluminateHttpRequest;
use AppModelsUser; //manualy imported
use IlluminateAuthEventsVerified;
use IlluminateFoundationAuthEmailVerificationRequest;
use CarbonCarbon;
class AuthController extends Controller
{
//
public function verifyEmail(Request $request, $id, $hash)
{
$user = User::find($id);
if (!$user) {
return response()->json(['message' => 'User not found.'], 404);
}
if (!hash_equals(sha1($user->getEmailForVerification()), $hash)) {
return response()->json(['message' => 'Invalid verification link.'], 400);
}
if ($user->email_verified_at) {
return response()->json(['message' => 'Email already verified.']);
}
$user->email_verified_at = Carbon::now();
$user->save();
return response()->json(['message' => 'Email verified successfully.']);
}
public function resendEmailVerification(Request $request)
{
if ($request->user()->hasVerifiedEmail()) {
return response()->json(['message' => 'Email already verified.']);
}
$request->user()->sendEmailVerificationNotification();
return response()->json(['message' => 'Verification email sent.']);
}
public function register(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|unique:users',
'password' => 'required|string|min:6'
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password)
]);
$user->sendEmailVerificationNotification();
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json(['token' => $token, 'user' => $user], 201);
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required'
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return ["error" => "Invalid credentials"];
}
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json(['token' => $token, 'user' => $user]);
}
public function logout(Request $request)
{
$request->user()->tokens()->delete();
return response()->json(['message' => 'Logged out']);
}
public function user(Request $request)
{
return response()->json($request->user());
}
}
These are my api routes
<?php
use AppHttpControllersAuthController;
use AppHttpControllersDisplayController;
use AppHttpControllersOrderController;
use AppHttpControllersStripeController;
use IlluminateHttpRequest;
use IlluminateSupportFacadesRoute;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Route::post("/login",[AuthController::class,"login"]);
Route::post("/register",[AuthController::class,"register"]);
Route::middleware('auth:sanctum')->post('/addOrder', [OrderController::class, 'addOrder']);
Route::middleware('auth:sanctum')->post('/pay', [StripeController::class, 'pay']);
Route::middleware('auth:sanctum')->get('/displayOrders', [DisplayController::class, 'displayOrders']);
Route::get('/email/verify/{id}/{hash}', [AuthController::class, 'verifyEmail'])
->middleware(['signed'])
->name('verification.verify');
And these are my web routes
<?php
use IlluminateSupportFacadesRoute;
use IlluminateFoundationAuthEmailVerificationRequest;
use AppHttpControllersAuthController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::get('/login', function () {
return redirect('http://localhost:5173/login');
})->name('login');
Route::get('/email/verify/{id}/{hash}', [AuthController::class, 'verifyEmail'])
->middleware(['auth:sanctum', 'signed'])
->name('verification.verify');
I already made sure that the user model has the email verification thing enabled. Simply , after I receive the confirmation email on mailtrap and click the button I am getting redirected to the login page and then when checking the data base the “email_verified_at” entry is still empty… pls help