Skip to content

Why Email Verification After Signup Is Critical for Your Web Application

I built a quick signup API with AI. It worked on the first try:

routes/auth.js
app.post('/api/signup', async (req, res) => {
const user = await db.createUser(req.body.email, req.body.password);
res.json({ success: true, user });
});

Clean, simple, done. I deployed it and forgot about it.

A week later I checked the user table and found 12,000 accounts. Most had emails like [email protected]. Someone had scripted my endpoint and created thousands of fake users. A few used real people’s email addresses — I found support tickets from angry users who got password reset emails they never requested.

That’s the problem with skipping email verification. Anyone can create an account with any email address. No proof required.

What goes wrong without verification

Three things happen in practice:

Fake accounts fill your database. Attackers find unprotected signup endpoints and bulk-create users. Your storage, rate limits, and daily-active-user metrics are all garbage now.

Impersonation. Someone signs up with [email protected]. Now they can request password resets, receive account-related emails intended for that person, and in some apps even access features tied to that “claimed” identity.

Broken password reset flow. Standard reset sends a link to the email on file. If the email isn’t verified, an attacker can pre-register someone else’s address, then click “forgot password” to receive the reset link. They don’t need to break in — they’re given the keys.

How to fix it: verify-first pattern

The fix is straightforward. Register the user but mark them inactive. Send a verification email. Only activate the account after they click the link.

routes/auth.js
const crypto = require('crypto');
app.post('/api/signup', async (req, res) => {
const token = crypto.randomBytes(32).toString('hex');
await db.createUser({
email: req.body.email,
verificationToken: token,
verified: false
});
await sendVerificationEmail(req.body.email, token);
res.json({ success: true, message: 'Check your email to verify' });
});
app.get('/api/verify-email', async (req, res) => {
const user = await db.findByVerificationToken(req.query.token);
if (!user || user.verificationExpires < Date.now()) {
return res.status(400).json({ error: 'Invalid or expired token' });
}
await db.activateUser(user.id);
res.json({ success: true, message: 'Email verified!' });
});

The key details:

  • Generate a random token with crypto.randomBytes — don’t use predictable values like user ID + timestamp
  • Set an expiration (1 hour is standard)
  • Store the token hashed in the database, not plaintext (same reason you hash passwords)
  • After verification, clear the token so it can’t be reused

Common mistakes I see

Allowing access before verification. If users can log in with an unverified account, the whole system is pointless. Block login, block API access, block everything until verified = true.

Tokens that never expire. A token sitting in a database for two years is a vulnerability waiting to happen. If the link leaks, the window of damage is infinite. Set verificationExpires and enforce it.

No rate limiting on verify endpoint. Attackers can brute-force tokens if you don’t limit requests. Use a sliding window rate limiter on /api/verify-email per IP.

Token in URL without sensitivity. The verification link goes in the user’s email, which passes through multiple servers. If you’re embedding something sensitive (like a session token) directly in the link, you’re leaking it. Use a single-use random token that can only be exchanged for account activation.


One afternoon of implementation saved me from thousands of bot accounts and an impersonation disaster. Don’t learn this the hard way — verify emails before handing over access.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments