Capa do Ebook gratuito Roteamento e Autenticação em React: Construindo SPAs Seguras com React Router e JWT

Roteamento e Autenticação em React: Construindo SPAs Seguras com React Router e JWT

Novo curso

20 páginas

Boas práticas de segurança no front-end: XSS, CSRF com cookies e hardening de UI

Capítulo 16

Tempo estimado de leitura: 14 minutos

+ Exercício
Audio Icon

Ouça em áudio

0:00 / 0:00

XSS (Cross-Site Scripting): o que é e por que SPAs são alvo frequente

XSS é uma classe de vulnerabilidade em que um atacante consegue fazer o navegador executar JavaScript malicioso dentro do contexto do seu domínio. Isso é crítico porque o script roda com as permissões do usuário logado: pode ler dados exibidos na tela, capturar interações, disparar requisições em nome do usuário e, dependendo de como sua aplicação lida com credenciais, até exfiltrar informações sensíveis.

Em SPAs com React, existe uma proteção importante por padrão: ao renderizar valores em JSX (por exemplo, {user.name}), o React faz escaping de caracteres e evita que HTML seja interpretado como markup. Porém, XSS ainda é possível quando você: injeta HTML diretamente, usa bibliotecas que inserem HTML no DOM, monta URLs perigosas, ou confia em dados não confiáveis para atributos e estilos.

Principais vetores de XSS em front-end

  • HTML não sanitizado renderizado via dangerouslySetInnerHTML ou via bibliotecas de markdown/HTML.
  • URLs controladas pelo usuário em href/src (ex.: javascript:), ou redirecionamentos abertos.
  • Injeção em atributos (ex.: on* handlers) quando se usa APIs de DOM diretamente.
  • DOM XSS ao usar innerHTML, insertAdjacentHTML ou construir HTML com concatenação de strings.
  • Dependências vulneráveis (bibliotecas de UI, markdown, editores WYSIWYG) que permitem payloads.

Como evitar XSS no React: práticas recomendadas

1) Prefira renderização segura (JSX) e evite HTML bruto

Regra prática: se o conteúdo veio do usuário (comentários, descrições, campos rich text) ou de uma API que pode conter dados não confiáveis, não renderize como HTML. Renderize como texto com JSX e deixe o React escapar.

// Seguro: React escapa automaticamente o conteúdo textual
function Comment({ text }) {
  return <p>{text}</p>;
}

2) Se precisar renderizar HTML, sanitize de forma explícita

Há casos legítimos: conteúdo de CMS, e-mails, artigos com formatação. Nesses casos, sanitize antes de renderizar. Uma abordagem comum é usar uma biblioteca de sanitização (ex.: DOMPurify). A sanitização deve ocorrer o mais próximo possível do ponto de renderização e com uma política restritiva (permitir apenas tags/atributos necessários).

// Exemplo conceitual (instale e configure uma biblioteca de sanitização)
import DOMPurify from 'dompurify';

function SafeHtml({ html }) {
  const clean = DOMPurify.sanitize(html, {
    USE_PROFILES: { html: true },
  });

  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

Boas práticas adicionais ao sanitizar:

Continue em nosso aplicativo

Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.

ou continue lendo abaixo...
Download App

Baixar o aplicativo

  • Bloqueie URLs perigosas em atributos como href e src (ex.: javascript:, data: quando não necessário).
  • Evite permitir atributos de evento (onclick, etc.). Sanitizadores bons já removem, mas valide.
  • Não confie em “sanitize no back-end” apenas. É útil, mas o front-end ainda deve tratar como não confiável.

3) Valide e normalize URLs antes de usar em links e redirecionamentos

Mesmo sem dangerouslySetInnerHTML, você pode criar problemas ao aceitar uma URL externa e colocá-la em href ou ao implementar redirecionamento pós-login. A prática é: permitir apenas caminhos internos (relativos) ou uma lista de domínios confiáveis.

// Permite apenas returnUrl interno (evita open redirect)
function safeReturnUrl(returnUrl) {
  if (!returnUrl) return '/';
  // só aceita caminhos relativos iniciando com '/'
  if (typeof returnUrl === 'string' && returnUrl.startsWith('/')) return returnUrl;
  return '/';
}

Para links externos fornecidos por usuários, prefira não renderizar como link clicável automaticamente. Se for necessário, normalize e valide o protocolo:

function isSafeHttpUrl(value) {
  try {
    const url = new URL(value);
    return url.protocol === 'http:' || url.protocol === 'https:';
  } catch {
    return false;
  }
}

4) Evite APIs de DOM que inserem HTML

Em React, quase sempre há alternativa declarativa. Evite element.innerHTML = ... e similares. Se precisar integrar com bibliotecas legadas, isole em um componente e sanitize a entrada.

5) Use Content Security Policy (CSP) para reduzir impacto

CSP é um cabeçalho HTTP configurado no servidor que restringe de onde scripts, estilos, imagens e conexões podem ser carregados. Mesmo que um XSS aconteça, uma CSP bem configurada pode impedir execução de scripts injetados (especialmente se você bloquear unsafe-inline e usar nonces/hashes).

Como prática, alinhe com o time de back-end/infra para:

  • Bloquear scripts inline e usar nonce/hashes quando necessário.
  • Restringir connect-src aos domínios de API esperados.
  • Restringir img-src e frame-src para evitar exfiltração e clickjacking.

No front-end, isso impacta como você inclui scripts e como lida com estilos inline. Planeje cedo para não depender de inline scripts.

CSRF com cookies: quando vira problema em SPAs

CSRF (Cross-Site Request Forgery) ocorre quando um site malicioso faz o navegador do usuário enviar uma requisição para o seu site, aproveitando que o navegador inclui automaticamente cookies de sessão. Isso é especialmente relevante quando sua autenticação usa cookies (por exemplo, cookie de sessão ou refresh token em cookie) e sua API aceita requisições state-changing (POST/PUT/PATCH/DELETE) sem uma verificação adicional.

Importante: CSRF não “rouba” o cookie; ele explora o fato de o cookie ser enviado automaticamente. Se sua API confia apenas no cookie para autenticar, um atacante pode induzir o navegador a disparar uma ação (ex.: trocar e-mail, fazer uma transferência, apagar um recurso) se não houver proteção.

Quando CSRF é menos relevante

  • Quando a API exige um token no header (ex.: Authorization: Bearer ...) e você não armazena esse token em cookies enviados automaticamente. Nesse caso, o atacante não consegue anexar o header por limitações do navegador (CORS e impossibilidade de ler tokens).
  • Quando endpoints sensíveis exigem confirmação adicional (reautenticação, MFA, senha), reduzindo impacto.

Quando CSRF é relevante

  • Quando você usa cookies para autenticação (sessão, access token em cookie, refresh token em cookie) e o navegador envia cookies automaticamente.
  • Quando você permite requisições cross-site com credenciais de forma permissiva.

Mitigações de CSRF em SPAs com cookies: checklist prático

1) Configure cookies com SameSite, Secure e HttpOnly

Essas flags são definidas no servidor ao emitir o cookie, mas o front-end precisa entender o efeito para não quebrar fluxos:

  • SameSite=Lax: geralmente a melhor opção padrão. Cookies não são enviados em muitas requisições cross-site, reduzindo CSRF. Pode afetar alguns fluxos de login via redirecionamento.
  • SameSite=Strict: mais restritivo; pode quebrar navegação legítima vinda de links externos.
  • SameSite=None; Secure: necessário para cenários cross-site (ex.: app em domínio diferente da API) quando você precisa enviar cookies. Exige HTTPS e aumenta a necessidade de anti-CSRF.
  • HttpOnly: impede leitura do cookie via JavaScript, reduzindo impacto de XSS para roubo de cookie (mas não impede CSRF).
  • Secure: cookie só via HTTPS.

Se seu front-end e API estão em domínios diferentes e você usa cookies, é comum precisar de SameSite=None; Secure. Nesse cenário, trate anti-CSRF como obrigatório.

2) Use token anti-CSRF (double submit ou sincronizador)

Uma estratégia comum é o servidor emitir um token CSRF e o front-end enviá-lo em um header customizado (ex.: X-CSRF-Token) em requisições mutáveis. O atacante não consegue ler o token (por SOP/CORS), então não consegue forjar o header corretamente.

Passo a passo típico (visão SPA):

  • 1) Ao carregar a aplicação (ou após login), o front-end chama um endpoint que retorna um token CSRF (ou o servidor o coloca em um cookie não-HttpOnly).
  • 2) O front-end armazena o token em memória (ou em storage, dependendo do modelo) e passa a enviá-lo em headers para POST/PUT/PATCH/DELETE.
  • 3) O servidor valida o token e rejeita se ausente ou inválido.
// Exemplo com fetch: incluir CSRF em requisições mutáveis
let csrfToken = null;

export async function initCsrf() {
  const res = await fetch('/api/csrf', { credentials: 'include' });
  const data = await res.json();
  csrfToken = data.csrfToken;
}

export async function apiFetch(url, options = {}) {
  const method = (options.method || 'GET').toUpperCase();
  const headers = new Headers(options.headers || {});

  const isMutating = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method);
  if (isMutating && csrfToken) {
    headers.set('X-CSRF-Token', csrfToken);
  }

  return fetch(url, {
    ...options,
    headers,
    credentials: 'include',
  });
}

Observações importantes:

  • Se o token CSRF expirar, trate 403/419 e reexecute initCsrf() antes de repetir a requisição (com cuidado para não criar loops).
  • Não use o mesmo token para sempre; rotacione conforme política do servidor.

3) CORS e credenciais: seja restritivo

Se você usa cookies cross-site, o front-end precisa enviar credentials: 'include' (fetch) ou withCredentials: true (axios). Isso aumenta a superfície: o servidor deve permitir apenas origens específicas e nunca usar curingas com credenciais.

Do lado do front-end, boas práticas:

  • Centralize a configuração de cliente HTTP para não “vazar” credentials para domínios indevidos.
  • Evite fazer chamadas para URLs arbitrárias controladas por usuário.

4) Proteja endpoints sensíveis com confirmação adicional

Mesmo com anti-CSRF, ações críticas (alterar e-mail, trocar senha, excluir conta, operações financeiras) devem pedir reautenticação, senha ou MFA. Isso reduz impacto de qualquer falha residual (CSRF, sessão sequestrada, dispositivo comprometido).

Hardening de UI: reduzindo vazamento de dados e ações indevidas no front-end

Hardening de UI é o conjunto de práticas para tornar a interface mais resistente a abuso, engenharia social e falhas de implementação que expõem dados ou permitem ações inesperadas. Não substitui validações do back-end, mas reduz risco e melhora a postura de segurança.

1) Não confie em “esconder” elementos como controle de acesso

Ocultar botões e rotas melhora UX, mas não é segurança. Ainda assim, é importante para reduzir tentativas acidentais e diminuir exposição de funcionalidades. A regra é: UI pode refletir permissões, mas o servidor deve impor.

Prática recomendada no front-end:

  • Renderize ações apenas quando o usuário tem permissão.
  • Desabilite e explique (tooltip/mensagem) quando a ação existe mas não está disponível.
  • Evite enviar requisições “para ver se pode” em massa; isso vira enumeração.

2) Evite vazar informações sensíveis em mensagens e logs

Erros detalhados ajudam desenvolvimento, mas podem expor dados em produção. Cuidados:

  • Não exiba mensagens do servidor diretamente se elas podem conter detalhes internos.
  • Não logue tokens, headers de autenticação, payloads sensíveis ou dados pessoais no console.
  • Em ferramentas de monitoramento no front-end, aplique mascaramento (redaction) para campos sensíveis.
// Exemplo: sanitizar logs de erro
function safeLogError(err) {
  const message = err?.message || 'Erro';
  // Evite imprimir objetos inteiros de request/response com headers
  console.error('[UI Error]', message);
}

3) Proteja contra clickjacking (defesa em profundidade)

Clickjacking ocorre quando sua aplicação é carregada dentro de um iframe de um site malicioso, que sobrepõe elementos para induzir cliques. A mitigação principal é via headers (X-Frame-Options ou CSP frame-ancestors) configurados no servidor. No front-end, evite depender de “frame busting” via JS, mas você pode adicionar detecção como camada extra em apps internos.

// Camada extra (não substitui headers): detectar se está em iframe
if (window.top !== window.self) {
  // Opcional: mostrar tela de bloqueio
  document.body.innerHTML = '';
}

4) Controle de foco, teclado e estados para evitar ações acidentais

Alguns bugs de UI viram incidentes: duplo clique que dispara duas compras, botões ativos durante carregamento, formulários que submetem repetidamente. Hardening inclui:

  • Desabilitar botões durante requisições.
  • Implementar idempotência no cliente quando possível (ex.: bloquear múltiplos submits).
  • Confirmar ações destrutivas com diálogos claros.
  • Evitar atalhos perigosos sem confirmação.
// Exemplo: prevenir múltiplos submits
function SaveButton({ onSave }) {
  const [loading, setLoading] = React.useState(false);

  async function handleClick() {
    if (loading) return;
    setLoading(true);
    try {
      await onSave();
    } finally {
      setLoading(false);
    }
  }

  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? 'Salvando...' : 'Salvar'}
    </button>
  );
}

5) Trate arquivos e uploads com cuidado na UI

Uploads são um vetor comum para abuso (arquivos gigantes, tipos inesperados). Mesmo que o servidor valide, a UI deve:

  • Restringir tipos aceitos (accept) e validar tamanho antes de enviar.
  • Não renderizar “pré-visualização” de HTML/SVG não confiável como markup.
  • Ao exibir arquivos enviados, preferir servir de um domínio separado (decisão de arquitetura) e usar headers adequados.
// Exemplo: validação básica antes do upload
function validateFile(file) {
  const maxBytes = 5 * 1024 * 1024;
  const allowed = ['image/jpeg', 'image/png'];
  if (!allowed.includes(file.type)) throw new Error('Tipo de arquivo não permitido');
  if (file.size > maxBytes) throw new Error('Arquivo excede o tamanho máximo');
}

6) Evite expor dados em caches e histórico quando não necessário

SPAs podem manter dados em memória, cache de requests e até persistir em storage. Para hardening:

  • Evite persistir PII desnecessária em localStorage/sessionStorage.
  • Ao lidar com telas sensíveis, considere limpar estados ao sair da rota.
  • Evite colocar dados sensíveis em query string (ela vai para histórico, logs e referer).

7) Proteja a UI contra enumeração e “user discovery”

Mensagens como “usuário não existe” vs “senha incorreta” podem facilitar enumeração. Mesmo que a decisão final seja do back-end, a UI pode padronizar mensagens para não amplificar o vazamento:

  • Use mensagens genéricas em autenticação (“Credenciais inválidas”).
  • Implemente delays e limites de tentativa no servidor; no cliente, evite feedback excessivamente detalhado.

Passo a passo: auditoria rápida de XSS, CSRF e hardening em uma SPA React

Passo 1: mapear pontos de entrada de conteúdo não confiável

  • Liste componentes que exibem texto vindo de usuários (comentários, perfis, descrições).
  • Procure uso de dangerouslySetInnerHTML e bibliotecas de markdown/WYSIWYG.
  • Procure manipulação direta do DOM (innerHTML, insertAdjacentHTML).

Passo 2: corrigir renderização de HTML

  • Substitua HTML bruto por renderização em JSX quando possível.
  • Quando HTML for necessário, sanitize com política restritiva e testes com payloads comuns.

Passo 3: revisar links, redirecionamentos e URLs

  • Garanta que returnUrl e redirecionamentos aceitam apenas caminhos internos.
  • Valide URLs externas e bloqueie protocolos não HTTP(S).
  • Em links externos, use rel="noopener noreferrer" quando target="_blank" for usado para reduzir riscos de tabnabbing.

Passo 4: se usar cookies, implementar anti-CSRF

  • Confirme com o back-end o modelo: SameSite, HttpOnly, Secure.
  • Implemente obtenção do token CSRF e envio em header para métodos mutáveis.
  • Centralize isso no cliente HTTP (wrapper fetch/axios) e trate expiração do token CSRF.

Passo 5: endurecer UI para ações críticas

  • Bloqueie múltiplos submits e adicione confirmações para ações destrutivas.
  • Padronize mensagens de erro para não vazar detalhes.
  • Revise telas que exibem PII e evite persistência desnecessária.

Passo 6: alinhar headers de segurança com infra/back-end

Mesmo sendo um capítulo focado em front-end, a segurança real depende de headers e políticas. Alinhe para habilitar:

  • CSP (com política compatível com sua build).
  • frame-ancestors/X-Frame-Options para clickjacking.
  • Referrer-Policy para reduzir vazamento via referer.
  • Permissions-Policy para limitar APIs do navegador quando aplicável.

Agora responda o exercício sobre o conteúdo:

Em uma SPA React que usa cookies para autenticação, qual medida reduz o risco de CSRF em requisições que alteram estado?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Com cookies, o navegador os envia automaticamente, o que permite CSRF se a API confiar apenas neles. Um token anti-CSRF em header customizado, validado no servidor, dificulta a forja da requisicao, pois o atacante nao consegue obter o token para montar o header corretamente.

Próximo capitúlo

Checklist de implementação: contratos, responsabilidades e pontos de auditoria

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.