← Voltar ao blog
· 4 min de leitura

Verificar Email via AJAX Antes do Submit: De jQuery Blur a Fetch e Tradeoffs de Segurança

De uma resposta de 2016 no Stack Overflow sobre verificar disponibilidade de email com jQuery AJAX até 2026 — fetch, AbortController, debouncing e por que enumeração de usuários é um risco real.

javascript formulários validação stackoverflow

Verificar Email via AJAX Antes do Submit: De jQuery Blur a Fetch e Tradeoffs de Segurança

Em 2016, eu respondi uma pergunta no Stack Overflow em Português sobre verificar se um endereço de email já estava cadastrado antes do usuário enviar o formulário. A abordagem: disparar uma requisição AJAX no evento blur do input, bater num endpoint do servidor que consulta o banco, e mostrar uma mensagem de erro se o email existisse. Escrevi com $.ajax() do jQuery. A resposta recebeu 4 upvotes.

A técnica era prática padrão. Todo formulário de cadastro fazia isso. Mas olhando em 2026, tem muito mais a considerar do que apenas “como fazer a chamada AJAX.”

A Resposta de 2016: jQuery AJAX no Blur

O padrão era simples:

$('#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 já cadastrado');
      } else {
        $('#email-error').text('');
      }
    },
  });
});

O endpoint do servidor consultava o banco, retornava um JSON indicando se o email existia, e o cliente mostrava ou escondia uma mensagem de erro. Direto ao ponto.

O problema não era que esse código tinha bugs — funcionava. Os problemas estavam em tudo ao redor.

O Que Tinha de Errado

Sem Debouncing

O evento blur dispara uma vez quando o usuário sai do campo, o que é ok. Mas muitas implementações usavam keyup, disparando uma requisição a cada tecla digitada. Digite “usuario@exemplo.com” e são 21 requisições HTTP. Sem debouncing, sem throttling, só uma avalanche de chamadas ao banco.

Sem Cancelamento de Requisição

Se o usuário digitasse rápido ou alternasse entre campos, múltiplas requisições podiam estar em andamento simultaneamente. Respostas podiam chegar fora de ordem, mostrando resultados obsoletos. Não tinha como cancelar uma chamada $.ajax() de forma limpa (tecnicamente xhr.abort() existia, mas ninguém usava nesse contexto).

Sem Rate Limiting

O endpoint ficava aberto. Qualquer pessoa podia scriptar requisições para enumerar todos os emails do seu banco. “usuario1@gmail.com existe? usuario2@gmail.com? usuario3?” Isso é um ataque de enumeração de usuários, e estávamos entregando de bandeja.

A Abordagem de 2026: Fetch, AbortController e Segurança

Código Client Moderno

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 já cadastrado');
  } catch (e) {
    if (e.name !== 'AbortError') throw e;
  }
}, 400);

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

Diferenças chave:

  • AbortController cancela a requisição anterior quando uma nova dispara — sem respostas obsoletas
  • Debouncing (400ms) previne enxurrada de requisições
  • fetch substitui o wrapper AJAX do jQuery — sem dependência de biblioteca para uma chamada HTTP

O Problema de Segurança Que Ninguém Falava

A verdade desconfortável: esse padrão inteiro é uma vulnerabilidade de enumeração de usuários. Se seu endpoint diz a um usuário não autenticado se um email existe no seu banco, um atacante pode montar uma lista de contas válidas. Isso é útil para:

  • Credential stuffing — testar senhas conhecidas contra contas confirmadas
  • Phishing — mirar em usuários que você sabe que têm conta
  • Engenharia social — “vi que você tem uma conta no…”

A abordagem moderna é não expor essa informação durante o cadastro. Em vez disso:

  1. Aceite o cadastro independentemente
  2. Se o email existe, envie um email para esse endereço dizendo “alguém tentou criar uma conta com seu email”
  3. Se não existe, envie o email de verificação normal

O usuário recebe a mesma UX (“verifique seu email para continuar”) independente da conta existir ou não. Nenhuma informação vaza. É o que Auth0, Firebase Auth e a maioria dos provedores de identidade fazem agora.

Quando Verificação em Tempo Real É Aceitável

Se sua app usa nomes de usuário (não emails) como identificador público, verificar disponibilidade em tempo real é menos arriscado — usernames já são públicos. Para cadastro baseado em email, o tradeoff de segurança raramente vale a conveniência de UX.

A Lição

A resposta de 2016 resolveu a questão técnica corretamente. Mas a pergunta em si — “como verifico se um email existe antes do submit?” — acaba tendo uma resposta melhor: “você provavelmente não deveria.” Às vezes a decisão correta de engenharia não é sobre como implementar uma feature, mas se deve implementá-la.

Posts Relacionados