Script Valley
Authentication From Scratch
Security HardeningLesson 5.5

How to implement email verification for new accounts

email verification flow, verification token generation, crypto.randomBytes, token expiry, database schema, resend verification, preventing login before verification

Verification Flow

Email verification confirms that a user controls the email address they registered with. Without it, users can register with fake or other people's emails, creating impersonation and spam risks.

const crypto = require('crypto');

// During registration, generate a token
const verificationToken = crypto.randomBytes(32).toString('hex');
const tokenExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24h

await db.query(
  `UPDATE users SET verification_token = $1, token_expiry = $2 WHERE id = $3`,
  [verificationToken, tokenExpiry, userId]
);

// Send email with link: https://yourapp.com/verify?token=TOKEN
// Verification endpoint
app.get('/verify', async (req, res) => {
  const { token } = req.query;
  const user = await db.findByVerificationToken(token);

  if (!user || user.tokenExpiry < new Date()) {
    return res.status(400).json({ error: 'Invalid or expired token' });
  }

  await db.query(
    `UPDATE users SET email_verified = true, verification_token = NULL WHERE id = $1`,
    [user.id]
  );
  res.json({ message: 'Email verified' });
});

Always give the same error for invalid and expired tokens. Different messages reveal whether a token ever existed. Use crypto.randomBytes — not Math.random() — for all security tokens.