← Back to blog
· 4 min read

Check Email via AJAX Before Submit: From jQuery Blur to Fetch and Security Tradeoffs

From a 2016 Stack Overflow answer on checking email availability with jQuery AJAX to 2026 — fetch, AbortController, debouncing, and why user enumeration is a real risk.

javascript forms validation stackoverflow

Check Email via AJAX Before Submit: From jQuery Blur to Fetch and Security Tradeoffs

In 2016, I answered a question on Stack Overflow in Portuguese about checking if an email address was already registered before the user submitted a form. The approach: fire an AJAX request on the input’s blur event, hit a server endpoint that checks the database, and show an error message if the email exists. I wrote it with jQuery’s $.ajax(). The answer scored 4 upvotes.

The technique was standard practice. Every registration form did it. But looking at it in 2026, there’s a lot more to consider than just “how to make the AJAX call.”

The 2016 Answer: jQuery AJAX on Blur

The pattern was simple:

$('#email').on('blur', function () {
  var email = $(this).val();
  $.ajax({
    url: '/check-email',
    method: 'POST',
    data: { email: email },
    success: function (response) {
      if (response.exists) {
        $('#email-error').text('Email already registered');
      } else {
        $('#email-error').text('');
      }
    },
  });
});

The server endpoint would query the database, return a JSON response indicating whether the email existed, and the client would show or hide an error message. Straightforward.

The problem wasn’t that this code was buggy — it worked. The problems were everywhere around it.

What Was Wrong With It

No Debouncing

The blur event fires once when the user leaves the field, which is fine. But many implementations used keyup instead, firing a request on every keystroke. Type “user@example.com” and that’s 16 HTTP requests. No debouncing, no throttling, just a barrage of calls to the database.

No Request Cancellation

If the user typed quickly or tabbed back and forth, multiple requests could be in flight simultaneously. Responses could arrive out of order, showing stale results. There was no way to cancel a pending $.ajax() call cleanly (technically xhr.abort() existed but nobody used it in this context).

No Rate Limiting

The endpoint was wide open. Anyone could script requests to enumerate every email in your database. “Does user1@gmail.com exist? user2@gmail.com? user3?” This is a user enumeration attack, and we were handing it out for free.

The 2026 Approach: Fetch, AbortController, and Security

Modern Client Code

let controller = null;

const checkEmail = debounce(async (email) => {
  if (controller) controller.abort();
  controller = new AbortController();

  try {
    const res = await fetch('/api/check-email', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email }),
      signal: controller.signal,
    });
    const data = await res.json();
    showError(data.available ? '' : 'Email already registered');
  } catch (e) {
    if (e.name !== 'AbortError') throw e;
  }
}, 400);

document.getElementById('email').addEventListener('input', (e) => checkEmail(e.target.value));

Key differences:

  • AbortController cancels the previous request when a new one fires — no stale responses
  • Debouncing (400ms) prevents a flood of requests
  • fetch replaces jQuery’s AJAX wrapper — no library dependency for an HTTP call

The Security Problem Nobody Talked About

Here’s the uncomfortable truth: this entire pattern is a user enumeration vulnerability. If your endpoint tells an unauthenticated user whether an email exists in your database, an attacker can build a list of valid accounts. This is useful for:

  • Credential stuffing — try known passwords against confirmed accounts
  • Phishing — target users you know have accounts
  • Social engineering — “I see you have an account on…”

The modern approach is to not expose this information at all during registration. Instead:

  1. Accept the registration regardless
  2. If the email exists, send an email to that address saying “someone tried to create an account with your email”
  3. If it doesn’t exist, send the normal verification email

The user gets the same UX (“check your email to continue”) whether the account exists or not. No information leaks. This is what Auth0, Firebase Auth, and most identity providers do now.

When Real-Time Check Is Acceptable

If your app uses usernames (not emails) as the public identifier, checking availability in real-time is less risky — usernames are already public. For email-based registration, the security tradeoff is rarely worth the UX convenience.

The Lesson

The 2016 answer solved the technical question correctly. But the question itself — “how do I check if an email exists before submit?” — turns out to have a better answer: “you probably shouldn’t.” Sometimes the right engineering decision isn’t about how to implement a feature but whether to implement it at all.

Related Posts