<h2>Introdução: O Cenário de Ameaças em Aplicações React</h2>
<p>Quando você constrói uma aplicação web com React, está criando interfaces que interagem com dados do usuário, APIs e bancos de dados. Essa interação constante cria superfícies de ataque que, se não forem adequadamente protegidas, podem comprometer a segurança de seus usuários e da aplicação inteira. React é uma biblioteca poderosa, mas não resolve magicamente problemas de segurança — cabe ao desenvolvedor implementar as camadas de proteção necessárias.</p>
<p>As três ameaças que discutiremos — XSS (Cross-Site Scripting), CSRF (Cross-Site Request Forgery) e a ausência de uma política CSP (Content Security Policy) robusta — são responsáveis por uma parcela significativa dos ataques web documentados. A boa notícia é que com conhecimento sólido e práticas conscientes, você consegue mitigá-las de forma eficaz.</p>
<h2>XSS (Cross-Site Scripting): Injecting Malicious Code</h2>
<h3>O que é XSS e por que é perigoso</h3>
<p>XSS ocorre quando um atacante consegue injetar código JavaScript malicioso que será executado no navegador de vítimas. Existem três tipos principais: <strong>Stored XSS</strong> (o código malicioso é armazenado no servidor e entregue a todos os usuários), <strong>Reflected XSS</strong> (o código é refletido via URL ou parâmetros) e <strong>DOM-based XSS</strong> (a vulnerabilidade está na manipulação do DOM pelo cliente).</p>
<p>Em uma aplicação React, o risco aumenta quando você trata dados não sanitizados como conteúdo HTML. Um atacante poderia injetar <code><img src=x onerror="alert('XSS')" /></code> em um campo de comentário, e se seu aplicativo renderizar isso diretamente, o código executará nos navegadores de todos que virem esse comentário.</p>
<h3>Proteção contra XSS no React</h3>
<p>React já oferece proteção padrão contra XSS ao escapar automaticamente conteúdo dinâmico inserido via JSX. Quando você escreve:</p>
<pre><code class="language-jsx">const userComment = '<img src=x onerror="alert(\'XSS\')" />';
export function CommentDisplay() {
return <div>{userComment}</div>;
}</code></pre>
<p>React escapará o conteúdo e o renderizará como texto literal, não como HTML. Essa é a maior defesa do React contra XSS.</p>
<p>Porém, existem situações legítimas onde você precisa renderizar HTML. Use <code>dangerouslySetInnerHTML</code> apenas quando absolutamente necessário, e sempre sanitize o conteúdo com bibliotecas como <code>DOMPurify</code>:</p>
<pre><code class="language-jsx">import DOMPurify from 'dompurify';
export function RichTextComment({ htmlContent }) {
const cleanHtml = DOMPurify.sanitize(htmlContent);
return (
<div dangerouslySetInnerHTML={{ __html: cleanHtml }} />
);
}</code></pre>
<p><code>DOMPurify</code> remove scripts, event handlers e outras estruturas perigosas, mantendo apenas as tags HTML seguras que você definir em uma whitelist. Sempre faça a sanitização no lado do cliente, mas nunca confie exclusivamente nela — valide e sanitize também no backend.</p>
<p>Outra prática importante é validar e escapar dados no servidor antes de enviá-los ao cliente. Se uma API retorna dados potencialmente contaminados, considere usar bibliotecas como <code>html-entities</code> para escapamento adicional:</p>
<pre><code class="language-jsx">import { decode } from 'html-entities';
export function SafeDisplay({ data }) {
// Se o servidor retornou dados com HTML encoding,
// decodifique-os de forma segura
const safeText = decode(data);
return <p>{safeText}</p>;
}</code></pre>
<p>Lembre-se: <strong>confie sempre que há validação em múltiplas camadas</strong>. Uma validação apenas no frontend não é suficiente.</p>
<h2>CSRF (Cross-Site Request Forgery): Forjando Requisições</h2>
<h3>Entendendo o ataque CSRF</h3>
<p>CSRF é um ataque onde um site malicioso força seu navegador a fazer requisições em nome de um site legítimo onde você está autenticado. Imagine que você está autenticado em seu banco online em uma aba. Você abre outra aba e acessa um site malicioso que contém <code><img src="https://banco.com/transferir?valor=1000&para=atacante" /></code>. Se seu navegador não tiver proteção, ele fará essa requisição usando seus cookies de autenticação.</p>
<p>O motivo pelo qual isso funciona é que cookies HTTP são enviados automaticamente com requisições cross-site (por padrão). O servidor não consegue distinguir se a requisição veio de uma ação legítima do usuário ou de um site malicioso.</p>
<h3>Proteção contra CSRF com Tokens</h3>
<p>A defesa padrão contra CSRF é usar <strong>tokens CSRF</strong>. O servidor gera um token único por sessão ou por requisição, o cliente deve incluir esse token em requisições que modificam dados (POST, PUT, DELETE), e o servidor valida o token antes de processar a ação.</p>
<p>Para implementar isso em React com uma API segura, primeiro você precisa que o servidor gere e retorne o token:</p>
<pre><code class="language-jsx">import { useEffect, useState } from 'react';
export function TransferForm() {
const [csrfToken, setCsrfToken] = useState('');
const [loading, setLoading] = useState(false);
// Buscar o token CSRF quando o componente monta
useEffect(() => {
fetch('/api/csrf-token', {
method: 'GET',
credentials: 'include' // Importante: inclui cookies
})
.then(res => res.json())
.then(data => setCsrfToken(data.token))
.catch(err => console.error('Erro ao buscar token CSRF:', err));
}, []);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch('/api/transfer', {
method: 'POST',
credentials: 'include', // Envia cookies de autenticação
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken // Inclui o token no header
},
body: JSON.stringify({
amount: 100,
toAccount: '12345'
})
});
if (response.ok) {
alert('Transferência realizada com sucesso!');
} else {
alert('Erro na transferência');
}
} catch (error) {
console.error('Erro:', error);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="number" placeholder="Valor" required />
<input type="text" placeholder="Conta destino" required />
<button type="submit" disabled={loading || !csrfToken}>
{loading ? 'Processando...' : 'Transferir'}
</button>
</form>
);
}</code></pre>
<p>No lado do servidor (exemplo com Express.js e middleware CSRF):</p>
<pre><code class="language-javascript">const csrf = require('csurf');
const cookieParser = require('cookie-parser');
const express = require('express');
const app = express();
app.use(cookieParser());
app.use(express.json());
// Middleware CSRF
const csrfProtection = csrf({ cookie: false, sessionKey: 'session' });
// Retorna o token CSRF para o cliente
app.get('/api/csrf-token', csrfProtection, (req, res) => {
res.json({ token: req.csrfToken() });
});
// Protege a rota de transferência
app.post('/api/transfer', csrfProtection, (req, res) => {
// Token foi validado automaticamente pelo middleware
// Se chegou aqui, é uma requisição legítima
const { amount, toAccount } = req.body;
// Processar transferência
res.json({ success: true });
});</code></pre>
<h3>SameSite Cookies: Camada Adicional</h3>
<p>Além de tokens CSRF, configure seus cookies com o atributo <code>SameSite</code>. Isso previne que cookies sejam enviados em requisições cross-site:</p>
<pre><code class="language-javascript">app.post('/api/login', (req, res) => {
// ... validar credenciais ...
res.cookie('sessionId', tokenValue, {
httpOnly: true, // Não acessível via JavaScript
secure: true, // Apenas HTTPS
sameSite: 'Strict' // Não envia em requisições cross-site
});
res.json({ success: true });
});</code></pre>
<p><code>SameSite=Strict</code> é a opção mais segura, mas pode afetar fluxos legítimos (como links de outros sites). Use <code>SameSite=Lax</code> para um equilíbrio entre segurança e usabilidade.</p>
<h2>Content Security Policy (CSP): Estabelecendo Limites</h2>
<h3>Conceito e Importância da CSP</h3>
<p>Content Security Policy é um header HTTP que define quais recursos (scripts, estilos, imagens, etc.) sua aplicação pode carregar. Ela funciona como uma whitelist que o navegador respeita, bloqueando qualquer recurso não autorizado.</p>
<p>Se um atacante conseguir injetar um <code><script></code> tag em sua página, ele será bloqueado se sua CSP não permitir scripts de fontes externas. Se ele tentar fazer uma requisição XHR para domínios não permitidos, o navegador bloqueará.</p>
<h3>Implementando CSP no React</h3>
<p>Configure o header CSP no seu servidor. Para uma aplicação React hospedada em <code>app.exemplo.com</code> que faz requisições para uma API em <code>api.exemplo.com</code>:</p>
<pre><code class="language-javascript">// Express.js
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'nonce-{random}'; " +
"style-src 'self' 'unsafe-inline'; " +
"connect-src 'self' https://api.exemplo.com; " +
"img-src 'self' data: https:; " +
"font-src 'self' data:; " +
"frame-ancestors 'none'; " +
"base-uri 'self'; " +
"form-action 'self'"
);
next();
});</code></pre>
<p>Nesta política:</p>
<ul>
<li><code>default-src 'self'</code>: Por padrão, permite recursos apenas do mesmo domínio</li>
<li><code>script-src 'self' 'nonce-{random}'</code>: Scripts apenas do mesmo domínio ou com um nonce específico</li>
<li><code>connect-src 'self' https://api.exemplo.com</code>: Requisições HTTP apenas para esses domínios</li>
<li><code>frame-ancestors 'none'</code>: Impede que sua aplicação seja embutida em iframes (previne clickjacking)</li>
<li><code>form-action 'self'</code>: Formulários podem fazer submit apenas para o mesmo domínio</li>
</ul>
<p>Para usar nonces (números usados uma única vez) que aumentam a segurança, gere um nonce único por requisição:</p>
<pre><code class="language-javascript">const crypto = require('crypto');
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('hex');
res.locals.nonce = nonce;
res.setHeader(
'Content-Security-Policy',
default-src 'self'; +
script-src 'self' 'nonce-${nonce}'; +
style-src 'self' 'unsafe-inline'
);
next();
});</code></pre>
<p>Depois, no seu HTML/template, inclua o nonce nos scripts inline:</p>
<pre><code class="language-html"><!DOCTYPE html>
<html>
<head>
<title>Minha App React</title>
</head>
<body>
<div id="root"></div>
<script nonce="<%= nonce %>">
// JavaScript inline aqui é permitido
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
</script>
</body>
</html></code></pre>
<h3>Testando e Ajustando CSP</h3>
<p>Para entender qual CSP sua aplicação precisa, use o modo "report-only" primeiro:</p>
<pre><code class="language-javascript">app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy-Report-Only',
"default-src 'self'; " +
"report-uri /csp-report"
);
next();
});
// Endpoint para receber relatórios de violação
app.post('/csp-report', express.json(), (req, res) => {
console.log('CSP Violation:', req.body);
res.sendStatus(204);
});</code></pre>
<p>Isso permite que você monitore quais recursos estão causando problemas sem bloquear nada. Quando estiver confiante, mude para o header oficial.</p>
<p>Ferramentas como CSP Evaluator do Google ajudam a identificar fraquezas em sua política:</p>
<pre><code class="language-plaintext"></code></pre>
<h2>Conclusão</h2>
<p>Ao dominar essas três camadas de segurança — <strong>proteção contra XSS através do escape automático do React e sanitização quando necessário</strong>, <strong>defesa contra CSRF com tokens e cookies SameSite</strong>, e <strong>restrição de recursos com CSP robusta</strong> — você cria uma aplicação significativamente mais segura. Não existe segurança perfeita, mas essas práticas implementadas juntas cobrem a maioria dos vetores de ataque comuns em aplicações web modernas.</p>
<p>O ponto crítico é entender que <strong>segurança é uma responsabilidade compartilhada</strong> entre frontend e backend. React protege você em algumas frentes, mas confiar exclusivamente nele é ingenuidade. <strong>Validação, sanitização e autenticação devem acontecer no servidor</strong>, enquanto o frontend adiciona camadas defensivas extras para melhorar a experiência do usuário e a resiliência geral.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html" target="_blank" rel="noopener noreferrer">OWASP XSS Prevention Cheat Sheet</a></li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html" target="_blank" rel="noopener noreferrer">OWASP CSRF Prevention Cheat Sheet</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" target="_blank" rel="noopener noreferrer">MDN Web Docs - Content Security Policy</a></li>
<li><a href="https://react.dev/learn/security" target="_blank" rel="noopener noreferrer">React Official Security Documentation</a></li>
<li><a href="https://github.com/cure53/DOMPurify" target="_blank" rel="noopener noreferrer">DOMPurify - Documentation</a></li>
</ul>
<p><!-- FIM --></p>