<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Boas práticas combinadas para apps Web modernas: Segurança, cache e Websocket

O desempenho individual da Web e as melhores práticas de segurança são amplamente documentadas, mas raramente são explicados juntos como um sistema coerente: TLS em todos os lugares, cookies de sessão configurado corretamente, sem JWT no localStorage, cache agressivo apenas onde for seguro fazê-lo, Revogação real de sessões e WebSockets, limitação de taxa em canais em tempo real e monitoramento de conexão todos esses sinais. Este guia abrange dez práticas que, aplicadas em conjunto, reduzem verdadeiramente o superfície de ataque sem sacrificar o desempenho percebido pelos usuários.

Cada seção inclui configurações concretas (Nginx, Express, NestJS, WebSocket) e as compensações reais de cada escolha - porque cada prática tem um custo, e aplicá-las sem entendê-lo leva a configurações culto à carga que realmente não protege nada.

1. HTTPS/WSS obrigatório

TLS não é negociável para HTTP ou WebSocket: sem wss://, um WebSocket de texto simples expõe tokens de sessão e cargas de aplicativos a qualquer pessoa na mesma rede. Configure o TLS 1.2 como mínimo (1,3 quando possível), HSTS com pré-carga e grampeamento OCSP para evitar latência uma verificação de certificado em tempo real em cada handshake.

# nginx.conf — TLS 1.2/1.3, HSTS, OCSP stapling
server {
  listen 443 ssl;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_stapling on;
  ssl_stapling_verify on;
  add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

  location /ws/ {
    proxy_pass http://backend_ws;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

2. Cookies de sessão: HttpOnly, Seguro, SameSite

HttpOnly impede que o JavaScript leia o cookie (mitiga o roubo via XSS), Secure envia apenas por HTTPS, SameSite controla o envio entre sites: Strict para proteção máxima contra CSRF (mas interrompe fluxos com redirecionamentos externos), Lax como um compromisso razoável para a maioria das aplicações, None somente se o cookie precisar ser verdadeiramente cross-site — e nesse caso requer Seguro obrigatório.

// Express/NestJS — cookie di sessione configurato correttamente
res.cookie('session_id', sessionToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 15 * 60 * 1000, // 15 minuti, coerente con la strategia di refresh
});

3. Nunca JWT em localStorage

localStorage pode ser lido por qualquer script executado na página: um único XSS (mesmo em uma dependência de terceiros, não em seu código) exfiltra o token sem precisar de qualquer interação usuário. A alternativa segura é o HttpOnly cookie para o próprio token, combinado com o padrão double submit cookie para proteção CSRF quando você também precisa de um mecanismo sem estado.

// Refresh token in HttpOnly cookie, access token short-lived in memoria (mai in storage persistente)
app.post('/auth/refresh', (req, res) => {
  const refreshToken = req.cookies.refresh_token; // HttpOnly, non leggibile da JS
  const newAccessToken = issueAccessToken(verifyRefreshToken(refreshToken));
  res.json({ accessToken: newAccessToken }); // vive solo in memoria lato client, mai in localStorage
});

4. Cache de ativos estáticos com impressão digital

Um ativo com hash no nome do arquivo (main.a1b2c3d4.js) pode ser armazenado em cache por um ano inteiro com imutável, porque cada alteração no conteúdo gera automaticamente um nome de arquivo diferente – invalidar o cache nunca requer uma limpeza manual.

# Angular CLI genera automaticamente asset con hash nel filename in build di produzione
ng build --configuration production
# Output: main.a1b2c3d4.js, styles.e5f6a7b8.css
# Header cache per asset fingerprinted
location ~* \.[0-9a-f]{8}\.(js|css)$ {
  add_header Cache-Control "public, max-age=31536000, immutable";
}

5. Não armazene em cache respostas confidenciais

Qualquer resposta da API com dados pessoais ou de sessão do usuário deve indicar explicitamente Cache-Control: no-store (nunca salvo, nem mesmo na memória temporária) ou private (armazenamento em cache apenas do navegador do usuário, nunca de um cache/CDN compartilhado). Se a resposta variar em com base em um cabeçalho como Authorization ou Accept-Language, declare-o com Vary para evitar que um cache intermediário forneça a resposta errada do usuário.

// NestJS — risposta con dati personali, mai cacheabile
@Get('me')
getProfile(@Res({ passthrough: true }) res: Response) {
  res.setHeader('Cache-Control', 'no-store');
  return this.userService.getCurrentProfile();
}

6. CDN com s-maxage separado do navegador

s-maxage controla por quanto tempo o CDN (cache compartilhado) mantém a resposta, independentemente de max-age controlar o navegador do usuário — isso permite forneça conteúdo quase estático da borda por minutos, mantendo seu navegador atualizado em minutos segundos ou vice-versa.

# Header: CDN cachea 5 minuti, browser solo 30 secondi
Cache-Control: public, s-maxage=300, max-age=30

# Purge mirata di un singolo path sulla CDN dopo un deploy
curl -X POST "https://api.cdn-provider.com/purge" -H "Authorization: Bearer $CDN_TOKEN" -d '{"path":"/api/products"}'

7. Atualizar e revogar tokens para sessões e WebSockets

Um token de acesso de curta duração (10 a 15 minutos) mais um token de atualização HttpOnly de maior duração limitam o janela de danos em caso de roubo. Real revocation requer estado do lado do servidor (uma lista negra ou contador de versão por usuário): um JWT puramente sem estado não pode ser invalidado antes do seu vencimento natural. Para WebSockets, a revogação deve fechar ativamente o conexão, não rejeite apenas solicitações HTTP subsequentes.

// Revoca sessione: invalida il refresh token e chiude ogni WebSocket associato all'utente
async function revokeSession(userId: string) {
  await sessionStore.incrementTokenVersion(userId); // invalida tutti i JWT emessi prima di ora
  for (const socket of wsConnections.getByUserId(userId)) {
    socket.close(4001, 'session_revoked');
  }
}

8. Limitação de taxa e limite de tamanho no WebSocket

Sem limites, um único cliente pode saturar o servidor com mensagens ou cargas úteis de alta taxa enorme — é a forma mais simples de aplicação DoS para executar em um canal não WebSocket protegido.

// ws — rate limiting e size limit per connessione
const wss = new WebSocketServer({ maxPayload: 64 * 1024 }); // 64KB max per messaggio

wss.on('connection', (socket) => {
  let messageCount = 0;
  const resetInterval = setInterval(() => (messageCount = 0), 1000);

  socket.on('message', (data) => {
    if (++messageCount > 20) return socket.close(4008, 'rate_limit_exceeded'); // max 20 msg/sec
    handleMessage(data);
  });

  socket.on('close', () => clearInterval(resetInterval));
});

9. Monitoramento

As métricas a serem rastreadas cobrem quatro áreas: conexões (WebSockets ativos, taxa reconexão), latência/taxa de transferência (tempo de resposta da API, mensagens/segundo), cache (taxa de acerto/erro para o CDN, TTL efetivo observado) e auth (taxa de falha de atualização de token, sessões revogadas). Prometheus + Grafana cobrem bem as métricas numéricos e dashboards em tempo real; ELK (ou equivalente) continua sendo mais adequado para registro de aplicativos pesquisa estruturada e ad hoc sobre eventos de segurança.

// Prometheus — contatore custom per connessioni WebSocket attive
const wsConnectionsGauge = new client.Gauge({ name: 'ws_active_connections', help: 'Connessioni WebSocket attive' });
wss.on('connection', () => {
  wsConnectionsGauge.inc();
  return () => wsConnectionsGauge.dec();
});

10. Teste de acessibilidade e privacidade

Verifique se nenhum cookie ou cabeçalho em cache expõe inadvertidamente dados pessoais (um Set-Cookie com email em texto simples no nome, uma resposta em cache público que contém o nome de usuário). Testes automatizados (axe-core para a11y, scanner de cabeçalho de segurança IC) cobrem casos objetivos; uma revisão manual periódica dos cabeçalhos Cache-Control em rotas com dados pessoais continuam necessárias porque um novo endpoint pode facilmente esquecer o cabeçalho correto.

Lista de verificação operacional rápida

  • Alta prioridade: HTTPS/WSS em todos os lugares, cookie de sessão HttpOnly+Secure+SameSite, remova qualquer JWT do localStorage.
  • Prioridade média: impressão digital de ativos estáticos, no-store cabeçalho em respostas com dados pessoais, limitação de taxa WebSocket.
  • Prioridade contínua: monitoramento de conexão/cache/autenticação, revisão periódica de cabeçalhos de cache em novas rotas.

Plano de 30/60/90 dias

Dias 1-30

  • HTTPS/WSS forçado em todos os lugares, HSTS ativo — KPI: 0 endpoints acessíveis em texto simples.
  • Cookies de sessão migrados para HttpOnly+Secure+SameSite — KPI: 0 tokens lidos do JavaScript do lado do cliente.

Dias 31-60

  • Limitação de taxa ativa em todos os canais WebSocket — KPI: 0 conexões capazes de exceder os limites definidos.
  • Cabeçalhos de cache corrigidos em todas as rotas com dados pessoais — KPI: auditoria completa, 0 respostas confidenciais armazenáveis publicamente em cache.

Dias 61-90

  • Painel de monitoramento completo (conexões, cache, autenticação) — KPI: alertas configurados em cada métrica crítica.
  • Revogação de Token e WebSocket testada de ponta a ponta — KPI: tempo efetivo de revogação inferior a 2 segundos.

FAQ

SameSite=Estrito ou Lax para um cookie de sessão?

Lax é o compromisso correto para a maioria das aplicações; Estrito apenas se você não tiver fluxos com redirecionamentos de domínios externos para o app.

Por que não usar apenas sessionStorage em vez de localStorage para o JWT?

Ambos são igualmente legíveis por JavaScript e, portanto, vulneráveis ao XSS: a diferença é apenas durabilidade, não segurança.

o-maxage funciona sem um CDN configurado?

Não, é ignorado pelos navegadores: afeta apenas caches compartilhados compatíveis, como CDNs ou proxies reversos que o suportam explicitamente.

Como posso revogar um JWT antes que ele expire?

Com estado do lado do servidor (lista negra ou versão de token por usuário), porque um JWT puro sem estado não pode ser invalidado antes da expiração natural.

Qual é o limite razoável de mensagens por segundo para um WebSocket?

Depende do caso de uso, mas 10 a 30 mensagens/segundo por conexão é um ponto de partida razoável para a maioria dos aplicativos em tempo real.

O grampeamento OCSP é obrigatório?

Não é obrigatório, mas é altamente recomendado: reduz a latência do handshake TLS, evitando que o cliente entre em contato com a autoridade de certificação separadamente.

Como evitar que uma CDN forneça uma resposta confidencial ao cliente errado?

Com Cache-Control: private ou no-store em respostas pessoais, nunca public quando os dados variam por usuário.

Você também precisa monitorar falhas de atualização de token?

Sim, um aumento repentino nas falhas de atualização costuma ser o primeiro sinal de um ataque contínuo ou bug de expiração de token.

Erros comuns a serem evitados

  • Cleartext WebSocket (ws://) atrás de um frontend HTTPS: O navegador só permite isso se o WebSocket estiver na mesma origem insegura, mas ainda expõe dados de texto simples à rede.
  • Cookies sem Secure em desenvolvimento também foram deixados em produção: frequentemente copiados de uma configuração de teste que nunca foi atualizada.
  • JWT em localStorage "temporariamente" durante o desenvolvimento: quase sempre se torna permanente porque "funciona" e ninguém o vê novamente.
  • Cache-Control ausente por padrão em novas rotas de API: sem um cabeçalho explícito, o comportamento do cache depende do cliente e não é garantido.
  • s-maxage e max-age idênticos: elimina a vantagem de poder invalidar o cache do navegador mais rapidamente do que o cache de borda.
  • Sem tamanho máximo em mensagens WebSocket: Um único cliente pode enviar cargas enormes e saturar a memória do servidor.
  • Revogar que bloqueia apenas solicitações HTTP futuras: Deixe as conexões WebSocket existentes ativas com o mesmo token comprometido.
  • Sem monitoramento de acertos/erros do cache: uma taxa de acertos decrescente geralmente sinaliza uma regressão nos cabeçalhos do cache que passou despercebida.

Respostas rápidas

Por que HTTPS também é necessário para WebSockets? Um WebSocket de texto simples (ws://) expõe tokens de sessão e cargas úteis de aplicativos para qualquer pessoa na mesma rede; wss:// criptografa toda a conexão exatamente como o HTTPS faz para solicitações HTTP tradicionais.

Qual é o sinalizador SameSite de um cookie? Controla se um cookie é enviado em solicitações entre sites: Strict bloqueia o envio entre sites, Lax permite apenas para navegação de nível superior, None sempre permite, mas requer Secure.

Por que não salvar um JWT no localStorage? Porque ele pode ser lido por qualquer script JavaScript em execução na página: um único XSS, mesmo em uma biblioteca de terceiros, permite que o token seja exfiltrado sem qualquer interação do usuário.

O que significa Cache-Control: imutável? Informa ao navegador que o conteúdo desse URL nunca mudará enquanto o URL permanecer o mesmo, evitando até mesmo uma solicitação de validação condicional durante o período de cache.

Como você revoga uma sessão com WebSockets ativos? Invalidando o estado do token no lado do servidor (lista negra ou versão) e fechando ativamente todas as conexões WebSocket associadas a esse usuário, não apenas rejeitando novas solicitações HTTP.

Qual é a diferença entre max-age e s-maxage? max-age verifica o cache do navegador do usuário, s-maxage verifica caches compartilhados, como CDN e proxy reverso, e quando presente tem precedência sobre max-age para esses caches.

Como verificar

  • Verifique os redirecionamentos TLS e HTTPS: curl -I https://seu-domínio.com e verifique o cabeçalho Strict-Transport-Security.
  • Inspecione o certificado e protocolo TLS: openssl s_client -connect your-domain.com:443 -tls1_2.
  • Verifique os cabeçalhos de cache nos ativos: curl -I https://your-domain.com/main.a1b2c3d4.js, verifique immutable e idade máxima.
  • Verifique se as respostas confidenciais não podem ser armazenadas em cache: curl -I https://your-domain.com/api/me, verifique no-store.
  • Simule a carga no WebSocket para validar a limitação de taxa: npx artilharia rápida --conte 100 -n 20 wss://seu-domínio.com/ws.
  • Teste a revogação ponta a ponta: revogue uma sessão via API e verifique se o WebSocket associado fecha em alguns segundos.

Conclusão

Nenhuma destas dez práticas é suficiente por si só: TLS sem cookies devidamente configurados ainda deixa a sessão vulnerável, cache agressivo sem distinguir as respostas públicas das privado expõe dados pessoais, e a limitação de taxa sem monitoramento não permite saber se é realmente funcionando. Aplicados em conjunto, com um plano de adoção priorizado (HTTPS e cookies primeiro, cache e limitação de taxa, monitoramento contínuo sempre), formam uma base de segurança e desempenho consistente para qualquer aplicativo da web moderno com componentes em tempo real.

Você deseja uma lista de verificação para impressão ou uma avaliação de sua configuração de segurança aplicação? Solicite uma auditoria técnica: em poucas horas de análise é possível identificar o lacunas prioritárias em cookies, cache, WebSockets e monitoramento.

💬 Notas dos leitores

0 notas

Escreva uma nota

Partilhe a sua opinião, uma sugestão ou um elogio

Notas recentes

Ainda não há notas. Seja o primeiro a comentar!