DEV Community

Davi Max
Davi Max

Posted on

ProfessorOS

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)