Programação Por Equipe DevBras

Capa do artigo: Login por matrícula sem quebrar o "esqueci a senha"

Login por matrícula sem quebrar o "esqueci a senha"

Antes de trocar o e-mail pela matrícula no login, garanta unicidade no banco, um canal de recuperação verificado e um fluxo de redefinição testado de ponta a ponta.

Começo de semestre, a Escola Técnica Horizonte troca o login do portal: em vez do e-mail, os 2.300 alunos entram com a matrícula. Na primeira manhã, a secretaria recebe 140 pedidos de senha nova. O "esqueci a senha" pedia a matrícula e mandava o link para o e-mail cadastrado, só que 312 alunos nunca tinham preenchido e-mail. E duas alunas descobriram que compartilhavam a mesma matrícula, herdada de um sistema antigo.

A resposta curta: trocar o identificador de login é mexer em três coisas juntas: a unicidade do novo campo, garantida pelo banco; o canal de recuperação, que precisa ser algo que a pessoa controla e que foi verificado; e o fluxo de redefinição inteiro, testado de ponta a ponta. Se o e-mail é o único jeito de recuperar a conta, ele não pode ser opcional.

Por que a troca parece simples e não é

Na tela, a mudança é um rótulo: "E-mail" vira "Matrícula". No código, é um campo diferente na consulta de autenticação. Por isso parece uma tarefa de meia hora. O problema é que o e-mail fazia dois papéis ao mesmo tempo, e só um deles foi trocado.

O e-mail era o identificador, aquilo que diz quem você afirma ser. E era o canal de recuperação, o lugar onde o sistema manda a prova de que você é você. A matrícula pode assumir o primeiro papel. Ela não assume o segundo: ninguém recebe mensagem numa matrícula. Se o fluxo de recuperação continua dependendo do e-mail, ele precisa existir, estar correto e ter sido confirmado pela pessoa.

Frameworks reforçam o tropeço. O fluxo padrão de redefinição de senha do Laravel, por exemplo, parte do e-mail. Mudar o campo do login sem revisar a redefinição deixa as duas pontas desencontradas. As questões de interface e de mensagem da troca de rótulo estão no post sobre login por matrícula; aqui o foco é o dado e a recuperação.

Como fazer a troca sem trancar ninguém para fora

  1. Audite os dados antes de mudar a tela. Procure matrículas repetidas, vazias e com espaços, e contas sem e-mail.
  2. Resolva as duplicidades com a secretaria, uma a uma, e só então crie um índice único no banco.
  3. Defina o canal de recuperação e quem não tem canal: e-mail verificado para a maioria, atendimento presencial com documento para o restante.
  4. Peça e verifique o e-mail de quem não tem, antes da troca, com um link de confirmação.
  5. Reescreva o "esqueci a senha" para receber a matrícula, mandar o link só para canal verificado e responder sempre a mesma mensagem.
  6. Aposente matrículas de quem saiu em vez de reaproveitá-las.
  7. Teste com uma conta nova, do cadastro à redefinição, não só o POST do login.

A auditoria em SQL

-- matrículas repetidas (com e sem espaços nas pontas)
SELECT TRIM(matricula) AS mat, COUNT(*) AS qtd
FROM usuarios
GROUP BY TRIM(matricula)
HAVING COUNT(*) > 1;

-- contas ativas sem canal de recuperação verificado
SELECT COUNT(*) AS sem_canal
FROM usuarios
WHERE ativo = 1
  AND (email IS NULL OR email = '' OR email_verificado_em IS NULL);

-- depois de resolver as duplicidades
UPDATE usuarios SET matricula = TRIM(matricula);
ALTER TABLE usuarios ADD UNIQUE KEY uk_usuarios_matricula (matricula);

Na Escola Horizonte, a primeira consulta trouxe 9 matrículas repetidas, quase todas por espaço no fim vindo de importação de planilha. O espaço invisível que quebra login quebra também a unicidade. A segunda consulta trouxe os 312 alunos sem e-mail verificado. Com esses números, a troca foi adiada duas semanas e a secretaria fez uma campanha de atualização cadastral. A primeira manhã do semestre seguinte teve 6 chamados, não 140.

O índice único não é opcional. Validação só na aplicação deixa passar duas inserções simultâneas e qualquer carga feita direto no banco. Com o índice, a segunda inserção falha e você trata o erro.

O "esqueci a senha" que parte da matrícula

O link de redefinição carrega um token aleatório. O banco guarda só o hash do token, com validade curta. A resposta para quem pede é sempre a mesma, exista a matrícula ou não, para o formulário não virar uma ferramenta de descobrir quem estuda ali.

<?php
function solicitarRedefinicao(PDO $pdo, string $matricula): void
{
    $stmt = $pdo->prepare(
        'SELECT id, email FROM usuarios
         WHERE matricula = ? AND ativo = 1 AND email_verificado_em IS NOT NULL'
    );
    $stmt->execute([trim($matricula)]);
    $usuario = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$usuario) {
        return;
    }

    $token = bin2hex(random_bytes(32));
    $pdo->prepare(
        'INSERT INTO redefinicoes_senha (usuario_id, token_hash, expira_em)
         VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 30 MINUTE))'
    )->execute([$usuario['id'], hash('sha256', $token)]);

    $link = 'https://portal.exemplo.edu.br/redefinir?token=' . $token;
    enviarEmail($usuario['email'], 'Redefinição de senha', 'Use este link em até 30 minutos: ' . $link);
}

A tela, em qualquer caso, mostra algo como "Se a matrícula estiver cadastrada com e-mail confirmado, enviamos as instruções. Se não receber, procure a secretaria com um documento." A função enviarEmail é a sua rotina de envio; o guia de formulário que chega ajuda a garantir que a mensagem não caia no spam.

Na hora de redefinir, busque pelo hash do token, confira que não expirou e que não foi usado, grave a nova senha com password_hash, marque o token como usado e encerre as outras sessões daquela conta. Coloque também um limite de pedidos por matrícula e por IP, como descreve o post sobre limite no esqueci a senha.

A matrícula de quem saiu não volta para a fila

Reaproveitar matrícula parece organizado e cria um risco silencioso: o novo aluno herda histórico, permissões e, às vezes, o e-mail de recuperação do anterior. Desative a conta, mantenha a matrícula reservada e gere uma nova para quem chega. Se a regra da instituição exige reaproveitar, a troca de titular precisa apagar o canal de recuperação antigo e exigir verificação do novo.

Erros comuns

  • Trocar o rótulo e manter a redefinição baseada em e-mail sem revisar: quem não tem e-mail fica trancado para fora no primeiro esquecimento.
  • Unicidade só na validação do formulário: duas importações ou dois cadastros simultâneos criam matrículas repetidas.
  • E-mail opcional sendo o único canal de recuperação: a conta não tem como ser recuperada sem atendimento humano.
  • Mandar o link para e-mail nunca verificado: um erro de digitação no cadastro entrega a conta para um desconhecido.
  • Mensagem diferente para matrícula inexistente: o formulário passa a revelar quem é aluno, como alerta o post sobre erro de login genérico.
  • Reaproveitar matrícula de quem saiu: o novo titular herda acesso e histórico que não são dele.

Checklist

  • A consulta de matrículas repetidas retorna zero linhas.
  • Existe índice único na coluna de matrícula.
  • Todas as contas ativas têm canal de recuperação verificado ou um procedimento presencial definido.
  • O link de redefinição só vai para e-mail confirmado.
  • O token é aleatório, guardado como hash, com validade curta e uso único.
  • A resposta do "esqueci a senha" é igual para matrícula existente e inexistente.
  • Matrículas de contas encerradas não são reatribuídas.
  • Uma conta nova passou por cadastro, login, esquecimento e redefinição num teste completo.

Pegue um usuário real do seu sistema sem e-mail preenchido: se ele esquecer a senha hoje, qual é exatamente o caminho para ele voltar a entrar?

Avalie este artigo

Referência: https://github.com/laravelbrasil/forum/issues/81