Autenticação na web
Você já tem hash, cifra, chave pública e TLS. Nada disso impede que a credencial vaze pela porta da frente — um script injetado numa página, um formulário disparado por outro site, uma senha reutilizada que apareceu num vazamento alheio. Esta lição fecha a trilha onde a segurança de verdade costuma falhar: na aplicação.
Uma pulseira de festival
HTTP não tem memória: cada requisição chega como se fosse a primeira. Provar quem você é a cada clique — digitando a senha de novo — seria insuportável. A solução é a pulseira de festival: você mostra o documento uma vez na entrada e recebe algo que, dali em diante, vale como identidade.
Toda a discussão sobre autenticação web decorre disso. A pulseira pode ser um número que só faz sentido para o portão (sessão) ou um crachá que já traz seus dados impressos e um selo (JWT). E, como qualquer pulseira, ela pode ser roubada — o que muda tudo é onde ela fica guardada e o que a impede de ser usada por outra pessoa.
/pedidos/1234 por /pedidos/1235 e ver o pedido de outra pessoa é a falha
de autorização mais banal e mais frequente que existe.
Sessão vs JWT, e os ataques na prática
Na primeira aba, compare os dois modelos e clique em "revogar o acesso" — é aí que a diferença aparece. Na segunda, combine o local de armazenamento do token com cada ataque e veja que nenhuma escolha vence tudo.
HTTP não tem memória: cada requisição chega sozinha, sem saber quem você é. Autenticação é resolver isso — e há duas famílias de solução, com um trade-off que costuma ser mal compreendido.
1. o servidor recebe o id da sessão
2. busca na tabela de sessões ← consulta a cada requisição
3. sabe quem é o usuário
Sessões no servidor
# HTTP não tem memória. Cada requisição chega sozinha, sem saber # quem você é. Autenticação na web é resolver isso. # --- ABORDAGEM 1: SESSÃO NO SERVIDOR --- # O cookie guarda um identificador OPACO, sem significado nenhum. # O estado real fica no servidor. sessoes["a3f9c2e1..."] = {"user_id": 42, "exp": ...} Set-Cookie: sessao=a3f9c2e1; HttpOnly; # JavaScript NÃO consegue ler → barra XSS Secure; # só viaja por HTTPS SameSite=Lax; # não vai junto em requisições de outro site → barra CSRF Path=/; Max-Age=86400 # O id precisa vir de um gerador CRIPTOGRAFICAMENTE seguro: import secrets secrets.token_urlsafe(32) # ✓ # random.random() é previsível — quem observar alguns ids consegue # prever os próximos e sequestrar sessões alheias. # ✓ revogação instantânea: basta apagar a linha # ✗ exige consultar o estado a cada requisição
JWT, e os dois enganos clássicos
# --- ABORDAGEM 2: JWT (token autocontido) --- # O token CARREGA os dados e uma assinatura. O servidor não guarda nada. # Três partes separadas por ponto: # header.payload.assinatura # eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiI0MiJ9 . k3NfVv8... # header: {"alg": "HS256", "typ": "JWT"} # payload: {"sub": "42", "exp": 1753280000} # assinatura: HMAC(chave_secreta, header + "." + payload) # ⚠ ENGANO Nº 1: o payload é BASE64, não criptografia. # Qualquer pessoa lê o conteúdo colando o token em jwt.io. # A assinatura impede ALTERAR, não LER. Nunca ponha dado sensível ali. # ⚠ ENGANO Nº 2: aceitar o algoritmo que o token declara. # O header vem do ATACANTE. A vulnerabilidade clássica: enviar # {"alg": "none"} e uma assinatura vazia. Bibliotecas antigas # aceitavam. Sempre fixe o algoritmo esperado no seu código: jwt.decode(token, chave, algorithms=["HS256"]) # nunca deduza do header # ✓ sem consulta de estado: valida com CPU apenas # ✗ NÃO DÁ PARA REVOGAR — o token vale até expirar sozinho # Mitigações: expiração curta (5-15 min) + refresh token, ou # uma lista de revogação — que reintroduz exatamente a consulta # de estado que o JWT prometia eliminar. # Quando o JWT vale a pena: vários serviços independentes que # precisam validar sem chamar um serviço central. # Para um app web comum com um banco de dados: sessão é mais # simples e mais segura. "Stateless" não é sinônimo de "melhor".
localStorage, qualquer JavaScript da página — inclusive o de uma dependência npm
comprometida — consegue lê-lo e exfiltrá-lo; em compensação, ele não viaja sozinho numa requisição
forjada, o que o imuniza a CSRF. Num cookie HttpOnly, o JavaScript não o alcança, mas o
navegador o anexa automaticamente, o que exige SameSite e token anti-CSRF. A prática
recomendada hoje é o cookie HttpOnly + Secure + SameSite=Lax: o XSS continua sendo grave,
mas a credencial não sai da máquina.
Os ataques que realmente acontecem
# XSS — Cross-Site Scripting # Código do atacante executa DENTRO da sua página, com todos os # privilégios do seu domínio. # ✗ elemento.innerHTML = entrada_do_usuario # ✓ elemento.textContent = entrada_do_usuario # ✓ frameworks (React, Vue) escapam por padrão — o perigo mora # nas escotilhas: dangerouslySetInnerHTML, v-html # ✓ Content-Security-Policy limita o que pode executar # ✓ cookie HttpOnly: se o XSS acontecer mesmo assim, ao menos # a credencial não pode ser EXFILTRADA para uso posterior # CSRF — Cross-Site Request Forgery # Outro site faz seu navegador enviar uma requisição autenticada. # Explora a gentileza do navegador de anexar cookies sozinho. # ✓ SameSite=Lax (padrão nos navegadores atuais) # ✓ token anti-CSRF nos formulários # ✓ conferir o cabeçalho Origin # FORÇA BRUTA e CREDENTIAL STUFFING # O segundo é mais comum e mais eficaz: testar e-mails e senhas # vazados de OUTRO site, apostando no reúso de senha. # ✓ rate limiting por IP e por conta # ✓ verificar a senha contra listas de vazamentos conhecidos # ✓ MFA — corta praticamente todo esse ataque # ENUMERAÇÃO DE USUÁRIOS # ✗ "e-mail não cadastrado" vs "senha incorreta" # revela quais e-mails existem no sistema # ✓ mensagem idêntica nos dois casos, e tempo de resposta também # (calcule o hash mesmo quando o usuário não existe) # TIMING ATTACK # ✗ if token_recebido == token_esperado ← para no 1º byte diferente # ✓ hmac.compare_digest(a, b) ← tempo constante
OAuth, MFA e passkeys
# OAUTH 2.0 / OPENID CONNECT — "entrar com o Google" # # OAuth é sobre AUTORIZAÇÃO (dar acesso limitado a um recurso); # OpenID Connect é a camada de AUTENTICAÇÃO em cima dele. # Confundir os dois é a origem de várias falhas conhecidas. # # O fluxo (authorization code + PKCE): # 1. o app manda o usuário ao Google # 2. o usuário se autentica LÁ — o app nunca vê a senha # 3. o Google devolve um código de uso único ao app # 4. o app troca o código por um token, pelo canal de trás # # O ganho: o app nunca toca na senha e recebe só o escopo pedido. # Use uma biblioteca — implementar OAuth do zero é um campo minado # de detalhes (validar state, nonce, aud, iss, expiração...). # MFA — o segundo fator, em ordem de força: # SMS — fraco: vulnerável a troca de chip (SIM swap) # TOTP — bom: código de 6 dígitos, derivado de um segredo # compartilhado + o relógio. Funciona offline. # WebAuthn — o melhor: resistente a PHISHING por construção # PASSKEYS (WebAuthn) — o fim da senha # Um par de chaves POR SITE. A privada nunca sai do dispositivo # (fica no Secure Enclave / TPM), liberada por biometria. # No login, o site manda um desafio e o dispositivo o assina. # # . não há segredo compartilhado → um vazamento no servidor # não expõe nada de útil # . o navegador só assina para o DOMÍNIO verdadeiro → o phishing # simplesmente para de funcionar, mesmo com o usuário enganado # # É a criptografia de chave pública desta trilha aplicada ao login — # exatamente o mesmo princípio das chaves SSH, com melhor usabilidade. # E a regra que vale mais que todas: NÃO IMPLEMENTE AUTENTICAÇÃO # DO ZERO. Use o provedor da sua plataforma ou uma biblioteca # madura e auditada.
Experimentando
Mini projeto: implemente login com sessão do zero num framework simples (Flask,
Express). Use secrets.token_urlsafe(32) para o id, bcrypt para a senha e todas as flags
de cookie. Depois quebre-o de propósito: troque o gerador por random.random() e mostre
que, observando alguns ids, dá para prever os próximos; tire o HttpOnly e leia o cookie
pelo console do navegador. Ver a falha funcionar fixa a defesa muito melhor que a recomendação.
Projeto principal: monte um laboratório de XSS e CSRF. Um "fórum" propositalmente
vulnerável em que comentários são inseridos com innerHTML, e uma página atacante noutra
porta. Escreva o comentário que rouba o token do localStorage e o envia para o seu
servidor. Depois aplique as defesas uma a uma — textContent, CSP, cookie
HttpOnly, SameSite — e verifique qual ataque cada uma para. Faça isso
apenas na sua máquina, contra a sua própria aplicação.
Desafio extra: implemente TOTP (RFC 6238) do zero — são cerca de 20 linhas: um HMAC
do contador de tempo, truncado a 6 dígitos. Gere o QR code, cadastre no seu app autenticador de
celular e confira que os códigos batem. Depois lide com o detalhe que a especificação não deixa
óbvio: a tolerância de relógio (aceitar a janela anterior e a seguinte) e a proibição de reusar um
código já consumido. Em seguida, experimente o WebAuthn no navegador com
navigator.credentials.create() e compare as duas experiências.
Teste sua intuição
HttpOnly num cookie faz?
Onde você encontra isso
Credential stuffing e o reúso de senha
A maior parte das invasões de conta hoje não quebra senha nenhuma: usa combinações de e-mail e senha vazadas de outro serviço, apostando no reúso. Bilhões de credenciais circulam em listas públicas. É por isso que gerenciadores de senha e MFA valem mais, na prática, que qualquer regra de complexidade — e por que exigir "uma maiúscula e um símbolo" resolve pouco se a senha é a mesma de sempre.
Phishing resistente por construção
Em 2017 o Google passou a exigir chaves de segurança físicas dos seus funcionários e relatou zero comprometimentos por phishing desde então. O motivo é estrutural, não educacional: mesmo que a pessoa caia no site falso, o navegador não produz assinatura para um domínio que não seja o registrado. É a diferença entre treinar humanos para não errar e desenhar um sistema em que o erro não tem consequência.
OWASP Top 10
A lista das dez classes de falha mais críticas em aplicações web, atualizada periodicamente. As primeiras posições são consistentemente ocupadas por falhas de controle de acesso — não por criptografia quebrada. É o resumo desta lição: o elo mais fraco quase nunca é a matemática, e sim a aplicação que esqueceu de verificar se aquele usuário podia mesmo acessar aquele recurso.