Cookies e gerenciamento de sessão

1. Fundamentos de Cookies no JavaScript

Cookies HTTP são pequenos arquivos de texto armazenados pelo navegador do usuário, contendo pares chave-valor que persistem entre requisições. Cada cookie possui atributos como domínio, path, data de expiração e flags de segurança.

Estrutura básica de um cookie:

nome=valor; Domain=exemplo.com; Path=/; Expires=Date; Secure; HttpOnly; SameSite=Lax

Lendo e escrevendo cookies com document.cookie

// Escrevendo um cookie simples
document.cookie = "usuario=joao; path=/; max-age=3600";

// Lendo todos os cookies
console.log(document.cookie); // "usuario=joao; preferencia=dark"

// Função para ler um cookie específico
function getCookie(nome) {
  const cookies = document.cookie.split('; ');
  for (const cookie of cookies) {
    const [chave, valor] = cookie.split('=');
    if (chave === nome) return decodeURIComponent(valor);
  }
  return null;
}

Atributos de segurança essenciais

// Cookie seguro com SameSite Strict
document.cookie = "token=abc123; Secure; SameSite=Strict; HttpOnly";

// Cookie com expiração personalizada
const dataExpiracao = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
document.cookie = `sessao=ativa; expires=${dataExpiracao.toUTCString()}; path=/`;
  • Secure: Cookie enviado apenas via HTTPS
  • HttpOnly: Inacessível via JavaScript (protege contra XSS)
  • SameSite: Controla envio em requisições cross-site (Strict, Lax, None)

2. Gerenciamento de Cookies no Node.js (Backend)

const express = require('express');
const cookieParser = require('cookie-parser');

const app = express();
app.use(cookieParser());

// Definindo cookie de resposta
app.post('/login', (req, res) => {
  res.cookie('sessionId', 'sessao_123', {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000 // 24 horas
  });
  res.json({ mensagem: 'Login realizado' });
});

// Lendo cookies enviados pelo cliente
app.get('/perfil', (req, res) => {
  const sessionId = req.cookies.sessionId;
  if (!sessionId) return res.status(401).json({ erro: 'Não autenticado' });
  res.json({ sessionId });
});

Opções avançadas com res.cookie()

app.get('/configurar-cookie', (req, res) => {
  res.cookie('preferencia', 'dark-mode', {
    domain: '.meusite.com',    // Disponível em subdomínios
    path: '/app',              // Restrito à rota /app
    expires: new Date('2025-12-31'),
    signed: true               // Cookie assinado para integridade
  });
  res.send('Cookie configurado');
});

3. Sessões no Servidor com Node.js

Sessões no servidor armazenam dados do usuário no backend, diferentemente de JWTs que são stateless. Isso oferece maior controle sobre revogação e segurança.

Implementação com express-session

const session = require('express-session');

app.use(session({
  secret: 'chave-super-secreta',
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    maxAge: 1000 * 60 * 60 // 1 hora
  }
}));

app.post('/login', (req, res) => {
  // Autenticação simulada
  req.session.userId = 123;
  req.session.role = 'admin';
  res.json({ mensagem: 'Sessão criada' });
});

app.get('/dashboard', (req, res) => {
  if (!req.session.userId) {
    return res.status(401).json({ erro: 'Faça login primeiro' });
  }
  res.json({ userId: req.session.userId, role: req.session.role });
});

Persistência com Redis (produção)

const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const redisClient = redis.createClient();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, secure: true, maxAge: 86400000 }
}));

4. Autenticação e Sessões no React (Frontend)

Envio automático de cookies com credentials: 'include'

// Configuração global do fetch
async function apiRequest(url, options = {}) {
  const response = await fetch(`http://localhost:3001${url}`, {
    ...options,
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers
    }
  });
  return response.json();
}

// Login no React
async function handleLogin(email, senha) {
  const data = await apiRequest('/login', {
    method: 'POST',
    body: JSON.stringify({ email, senha })
  });
  return data;
}

Gerenciamento de estado com Context API

const AuthContext = React.createContext();

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Verificar sessão ao carregar
    apiRequest('/verificar-sessao')
      .then(data => setUser(data.user))
      .catch(() => setUser(null))
      .finally(() => setLoading(false));
  }, []);

  const login = async (email, senha) => {
    const data = await handleLogin(email, senha);
    setUser(data.user);
  };

  const logout = async () => {
    await apiRequest('/logout', { method: 'POST' });
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, loading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

5. Segurança em Cookies e Sessões

Proteção contra CSRF

// Backend - Geração de token CSRF
const crypto = require('crypto');

app.use((req, res, next) => {
  if (!req.session.csrfToken) {
    req.session.csrfToken = crypto.randomBytes(32).toString('hex');
  }
  res.cookie('csrf-token', req.session.csrfToken, { httpOnly: false, sameSite: 'strict' });
  next();
});

// Middleware de validação CSRF
function csrfProtection(req, res, next) {
  const tokenHeader = req.headers['x-csrf-token'];
  if (tokenHeader !== req.session.csrfToken) {
    return res.status(403).json({ erro: 'CSRF token inválido' });
  }
  next();
}

Prevenção contra XSS

// Frontend - Sanitização de entrada
function sanitizarInput(input) {
  const div = document.createElement('div');
  div.textContent = input;
  return div.innerHTML;
}

// Backend - Headers de segurança
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('Content-Security-Policy', "default-src 'self'");
  next();
});

6. Integração Completa: Exemplo Prático

Backend (Node.js + Express)

const express = require('express');
const session = require('express-session');
const bcrypt = require('bcrypt');

const app = express();
app.use(express.json());
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000 }
}));

// Middleware de autenticação
function authMiddleware(req, res, next) {
  if (!req.session.userId) return res.status(401).json({ erro: 'Não autorizado' });
  next();
}

// Registro
app.post('/register', async (req, res) => {
  const { email, senha } = req.body;
  const hash = await bcrypt.hash(senha, 10);
  // Salvar no banco...
  res.json({ mensagem: 'Usuário criado' });
});

// Login
app.post('/login', async (req, res) => {
  const { email, senha } = req.body;
  // Verificar credenciais...
  req.session.userId = 1;
  res.json({ mensagem: 'Login bem-sucedido' });
});

// Rota protegida
app.get('/dados-protegidos', authMiddleware, (req, res) => {
  res.json({ dados: 'Informação sensível' });
});

// Logout
app.post('/logout', (req, res) => {
  req.session.destroy(err => {
    if (err) return res.status(500).json({ erro: 'Erro ao fazer logout' });
    res.clearCookie('connect.sid');
    res.json({ mensagem: 'Logout realizado' });
  });
});

Frontend (React)

function Login() {
  const [email, setEmail] = useState('');
  const { login } = useContext(AuthContext);

  const handleSubmit = async (e) => {
    e.preventDefault();
    await login(email, 'senha123');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
      <button type="submit">Entrar</button>
    </form>
  );
}

function RotaProtegida() {
  const { user, loading } = useContext(AuthContext);

  if (loading) return <div>Carregando...</div>;
  if (!user) return <Navigate to="/login" />;

  return <div>Bem-vindo, {user.email}</div>;
}

Referências