KAHIBARO
Discord Login Register

13.15. Email Verification

Why Email Verification Matters

When a user signs up to your application, you usually want to confirm that:

  1. The email address actually exists.
  2. The person signing up controls that inbox.
  3. You can safely use that email for password resets, notifications, and security alerts.

This is what email verification solves.

Typical goals of email verification:

In most systems, email verification is optional for basic browsing, but required for high risk actions like changing password, making payments, or accessing personal data.

Important: Never treat an unverified email as a trusted way to prove identity. Only a verified email can be used for password reset or other sensitive actions.


Common Email Verification Flow

Although implementations differ, most backends follow this basic flow:

  1. User registers
    • User submits email, password, and maybe name.
    • Backend creates a user record with is_email_verified = false.
  2. Backend generates a verification token
    • Token is random and unpredictable.
    • Token is associated with the user and has an expiration time.
  3. Backend sends a verification email
    • Email contains a unique link with the token in it, something like:
      https://example.com/verify-email?token=XYZ
  4. User clicks the link
    • Browser sends a request to your backend with the token.
    • Backend validates the token, checks expiry, and finds the user.
  5. Backend marks email as verified
    • Set is_email_verified = true on the user.
    • Invalidate or delete the token so it cannot be reused.
    • Optionally redirect to a “Email verified” page or auto log in.
  6. Optional: enforce verification checks
    • Block login, or restrict actions, if is_email_verified is false.

Here is a very high level sequence:


StepActorAction
1ClientPOST /register with email and password
2ServerCreate user, generate token, save token
3ServerSend email with /verify-email?token=...
4UserClicks link in email
5ClientBrowser GET /verify-email?token=...
6ServerValidate token, mark email verified, redirect/show page

Verification Tokens

The core of email verification is the token. This token proves that whoever clicked the link had access to the email inbox.

What the Token Should Be

A verification token must be:

There are two common styles:

  1. Opaque random token
    • Example: c31f5a8d6b854f92a1b7db9a69d9b33
    • Stored in the database, usually hashed.
  2. Signed token (like a JWT or a signed string)
    • Contains user info and an expiry timestamp.
    • Backend can verify it without a database lookup for the token itself.

Random Token Example

In Python (conceptual example, not full code):

python
import secrets
def generate_verification_token() -> str:
    return secrets.token_urlsafe(32)  # e.g. 'aK8...'

You could store it in a table like:

ColumnTypeExample
idint (PK)1
user_idint (FK)42
token_hashtextsha256(token)
created_attimestamp2026-08-27 10:00:00
expires_attimestamp2026-08-27 11:00:00
used_attimestampnull or 2026-08-27 10:30:00 after verification

You never store the token itself, only a hash of it:

python
import hashlib
def hash_token(token: str) -> str:
    return hashlib.sha256(token.encode("utf-8")).hexdigest()

Rule: Treat email verification tokens like passwords.
Never log them, never expose them to analytics, and if you store them, store only a hash, not the raw token.

Signed Token Example

Instead of saving tokens in a table, you can sign the data and verify the signature when the user clicks:

At verification time:

  1. Decode and verify the signature.
  2. Check expiry.
  3. Find the user and verify the email.

You still may want to store some state in your database (for example, when the user was verified), but you do not need a separate token table.


Designing the Database Fields

You need at least one field on your user table for verification status.

Typical user fields:

FieldTypeDescription
idint (PK)User ID
emailtextUnique email
password_hashtextHashed password
is_email_verifiedbooleanTrue if email is verified
email_verified_attimestampWhen the email was verified

Where to store tokens:

For beginners, a dedicated table is easy to understand and secure.


Generating and Sending Verification Emails

When to Send the Email

You usually send a verification email:

Each of these flows should:

  1. Generate a new token.
  2. Invalidate or mark previous tokens related to the same email.
  3. Send a fresh email with the new link.

What the Email Should Contain

A typical verification email includes:

Example text:

Subject: Confirm your email for ExampleApp

Hi Alice,

Thank you for creating an account on ExampleApp.

Please confirm your email by clicking this link:
https://example.com/verify-email?token=XYZ

This link will expire in 60 minutes.

If you did not create this account, you can safely ignore this email.

Constructing the Verification Link

The link usually contains the token as:

Or

Query parameter style is more common and easier to copy and paste.

Backend route examples:

In a real app, that route might:

  1. Call your API.
  2. Show a confirmation page in the browser.
  3. Or redirect to a SPA (single page app) that displays the result.

Important: Never put sensitive data such as passwords or password hashes into the verification link. The link should contain only the token and maybe non sensitive data like a redirect URL.


Verifying the Token

When a request arrives at your verification endpoint:

  1. Read the token from the request.
    • From query: token=....
    • From path: /verify-email/{token}.
  2. Validate the token format.
    • If the token is missing or too short, return an error.
  3. Look up the token.
    • If using a token table, hash the token and search for the hash.
    • If using a signed token, verify the signature and parse its data.
  4. Check token state:
    • Not expired.
    • Not already used.
    • Matches the right user and email.
  5. Mark user as verified:
    • Set is_email_verified = true.
    • Set email_verified_at = now.
    • Optionally set used_at = now on the token or delete the token.
  6. Respond to the client:
    • Redirect to a success page.
    • Or return a JSON response like { "detail": "Email verified" }.

Example JSON responses:

json
{
  "detail": "Email successfully verified."
}
json
{
  "detail": "Invalid or already used verification token."
}
json
{
  "detail": "Verification link has expired. Please request a new one."
}

You can use different HTTP status codes:


SituationStatus code
Successful verification200 or 302
Missing or invalid token400
Expired or used token400 or 410
Internal server error500

Token Expiration and Resending

Choosing an Expiration Time

If the token never expires, anyone who gains access to the email at any time can verify the account. Also, old links floating around are not great for security.

Common expiration choices:

ExpirationWhen it makes sense
15 minutesHighly sensitive apps, banking, security heavy
1 hourMost typical web apps
24 hoursVery low friction needed, less strict

Rule: Email verification tokens must have an expiration time.
A common choice is 1 hour.

Resending the Verification Email

You must allow users to ask for a new verification email, especially if:

A typical endpoint might be:

Behavior:

  1. Check if the user is already verified.
    • If yes, respond with "Email already verified." and do nothing.
  2. Generate a new token.
  3. Invalidate or mark older verification tokens as used or expired.
  4. Send a new email.

To avoid abuse:

Returning a neutral response is often better:

"If there is an unverified account with this email, a new verification email has been sent."

This avoids revealing whether an email is registered.


Linking Email Verification With Registration

Typically, the backend does this directly after a successful registration:

  1. Create the user with is_email_verified = false.
  2. Generate a token.
  3. Send a verification email.

Example pseudo-steps:

json
{
  "detail": "Registration successful. Please check your email to verify your account."
}

Login should then check is_email_verified:

You must design this based on your product requirements.


Security Considerations

Email verification looks simple, but there are common security pitfalls.

1. Token Strength

Tokens must be sufficiently random and long. Using secrets.token_urlsafe(32) or equivalent is a safe baseline.

Avoid:

2. Token Storage

Storing tokens in plain text is risky:

Better approach:

3. One Time Use

A verification link should be usable once.

After a successful verification:

Even if someone copies the link from an old email, it should not work again after it has been used.

4. No Account Enumeration

Your responses should not reveal whether an email is registered.

Cases:

This prevents attackers from using your API as an “email check” service.

5. Protecting the Verification Endpoint

Even though the endpoint is not as sensitive as login, you still need:

Important: Always use HTTPS on verification links.
Sending a token over plain HTTP lets attackers on the network intercept and hijack accounts.


User Experience Considerations

Good UX around email verification reduces user frustration and support tickets.

Clear Messages

On registration:

On verification pages:

Automatic Login After Verification

Some apps automatically log the user in after the email is verified. Others ask for login again.

Options:

Both are valid; pick what matches your security and UX goals.

Mobile and Email Clients

Consider that:

To mitigate prefetch issues:

Handling Edge Cases

Real systems must handle scenarios that are not part of the “happy path”.

User Changes Email Address

When a user changes their email:

  1. Keep is_email_verified = false for the new email.
  2. Clear email_verified_at.
  3. Generate a new verification token.
  4. Send a new verification email to the new address.

You might also want to:

Multiple Verification Attempts

What if the user clicks the same link twice?

Do not treat it as an error to avoid confusing users.

Token Stolen

If you suspect a token might be compromised:

Putting It All Together: Example Flow

Here is a simple end to end example of how the main endpoints could interact, in a backend agnostic way.

1. Register

json
{
  "email": "alice@example.com",
  "password": "supersecret"
}

https://app.example.com/verify-email?token=T

json
{
  "detail": "Registration successful. Please check your email to verify your account."
}

2. Verify Email

json
{
  "detail": "Email successfully verified."
}

3. Resend Verification

json
{
  "email": "alice@example.com"
}
json
{
  "detail": "If an unverified account exists with this email, a new verification email has been sent."
}

Summary

Email verification connects your registration system with a real user identity channel, the email inbox. A solid implementation:

By designing tokens, storage, and flows carefully, you avoid common pitfalls and create a safer, more reliable backend for your users.

Views: 10

Comments

Please login to add a comment.

Don't have an account? Register now!