Quando comecei a construir o ProfessorOS — um sistema pessoal para gerenciar minhas turmas, notas e frequência — cheguei ao ponto que todo projeto com login enfrenta: como fazer a autenticação.
A resposta mais rápida seria instalar uma biblioteca pronta e seguir em frente. Decidi não fazer isso. Não por implicância com bibliotecas, mas porque autenticação é exatamente o tipo de coisa que, se você nunca entendeu por dentro, vira uma caixa-preta que você só reza para estar configurada certo. Preferi construir a minha, entender cada decisão, e só então confiar nela.
Aqui está o que ficou de pé no final.
Senha não se guarda, se transforma
A primeira decisão é a mais óbvia, mas vale reforçar: senha em texto puro no banco de dados não é opção, nem por um segundo. Uso bcrypt, que não é só um algoritmo de hash — é um hash deliberadamente lento.
Isso é intencional. Um hash rápido (como MD5 ou SHA-256 sozinho) permite que um invasor teste bilhões de combinações por segundo se o banco vazar. O bcrypt inclui um "custo" configurável que torna cada tentativa mais cara computacionalmente — lento o suficiente para não incomodar um login legítimo, e caro o suficiente para inviabilizar um ataque de força bruta em escala.
Um cookie que não pode ser falsificado
Depois do login, o sistema precisa lembrar quem você é nas próximas requisições. A forma mais simples seria guardar um ID de sessão em um cookie e comparar com o banco a cada requisição.
Fui um passo além: o cookie de sessão é assinado com HMAC-SHA256. Na prática, isso significa que o conteúdo do cookie carrega uma "assinatura" matemática gerada com uma chave secreta que só o servidor conhece. Se alguém tentar alterar o conteúdo do cookie manualmente — trocar o ID de usuário, por exemplo — a assinatura não bate mais, e o servidor rejeita a sessão na hora.
O cookie também é httpOnly, o que impede que ele seja lido por JavaScript no navegador — uma proteção direta contra ataques de XSS tentando roubar a sessão.
Duas camadas de verificação, não uma
Essa foi a parte que mais me fez repensar o próprio desenho do sistema. Segui um padrão recomendado pela própria documentação do Next.js: verificar a sessão em dois lugares diferentes, com propósitos diferentes.
- Uma verificação otimista, na borda (
proxy.ts), que só confere se existe um cookie com formato válido — rápida, usada para decidir se redireciona o usuário para a tela de login antes mesmo de carregar a página. - Uma verificação definitiva, na camada de acesso a dados (
auth.ts), que valida a assinatura do cookie de verdade e confere permissões — essa é a que decide se uma ação sensível (ver notas de um aluno, editar uma turma) realmente acontece.
A lógica é simples: a primeira camada existe para dar uma resposta rápida ao usuário; a segunda existe porque a primeira, sozinha, nunca deveria ser a única linha de defesa.
Limitando tentativas de login
Por fim, um limite de tentativas: 5 tentativas de login a cada 15 minutos por IP, com um teto global de 30 tentativas no sistema todo. Sem isso, toda a engenharia de hash e assinatura acima ainda deixaria uma porta aberta para um ataque simples de tentativa e erro repetido.
O que fica disso
Nenhuma dessas quatro peças é complexa isoladamente. O que exige cuidado é a soma delas — e entender por que cada uma existe, não só copiar um trecho de código que "funciona".
Como professor, isso mudou até a forma como falo sobre segurança em sala: não como uma lista de boas práticas para decorar, mas como um conjunto de perguntas — o que essa camada impede, especificamente, que aconteça? Se a resposta não é clara, a camada provavelmente não deveria estar ali só por estar.
Top comments (0)