I'm trying to implement email verification in a MERN stack app, and while a big deal of it works, I'm struggling to handle the case when the token is either invalid or expired.
To try and be brief, this is more or less what happens:
User registers
Towards the end of the registration function (backend) I have:
// Create user
const user = new User({
name,
email,
password: hashedPassword,
});
// Email verification
jwt.sign(
{
user: user._id,
},
process.env.JWT_SECRET,
{
expiresIn: 3600,
},
(err, emailToken) => {
mailTransport().sendMail({
from: process.env.USER_EMAIL,
to: user.email,
subject: 'Verify your email address',
html: generateEmail(emailToken),
});
}
);
await user.save();
The function generateEmail()
creates and emails a verification link such as:
${process.env.FRONTEND}/verify-email/${token}
The React component in the frontend, VerifyEmail.jsx
is roughly like this (deleted redundant code):
const API_URL = '/api/users/verify-email';
const VerifyEmail = () => {
const { verificationToken } = useParams();
const navigate = useNavigate();
const onVerify = async () => {
try {
await axios.get(`${API_URL}/${verificationToken}`);
toast.success('Email verified. You can now log in');
navigate('/login');
} catch (error) {
toast.error(error);
}
};
return (
<section>
<h1 className="text-6xl border-b-2 pb-2">Verify your email</h1>
<p className="my-5">Please click below to verify your email</p>
<button type="button" className="btn btn-info" onClick={onVerify}>
Verify Email
</button>
</section>
);
};
The backend route for that request is:
router.get('/verify-email/:verificationToken', verifyToken);
And the function verifyToken
is:
const verifyToken = asyncHandler(async (req, res) => {
try {
const { user } = jwt.verify(
req.params.verificationToken,
process.env.JWT_SECRET
);
await User.findByIdAndUpdate(user, { verified: true }, { new: true });
} catch (error) {
res.status(400);
throw new Error(error.message);
}
return res.redirect(process.env.FRONTEND);
});
All this effectively verifies the email and updates the user in the database with verified: true
BUT it doesn't handle when the token is invalid or expired.
What am I missing?
Need to add that the only error I get is, in the console, a 400
saying I'm attempting a GET
request on the API on the frontend (port 3000) rather than the backend (5001):
GET http://localhost:3000/api/users/verify-email/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiNjFmZDUyNzFhNzIyMjNjZWE4MGM0MDcxIiwiaWF0IjoxNjQzOTkxNjY1LCJleHAiOjE2NDM5OTUyNjV9.zyWhGtfV2coNZKYVW6yUOUbHo6gBnZZ4aRU79OE0Nbw
(Only when the JWT is expired or invalid)