`Cookie` em TypeScript

`Cookie` em TypeScript

Este artigo explica Cookie em TypeScript.

Apresentamos padrões práticos para lidar com cookies de forma segura e confiável tanto no navegador quanto no servidor.

YouTube Video

Cookie em TypeScript

Conceitos básicos de cookies

Um cookie é um mecanismo para armazenar pequenas strings (pares nome=valor) no cliente, criado por meio do cabeçalho HTTP Set-Cookie ou de document.cookie. Você pode controlar seu comportamento com atributos de segurança (HttpOnly, Secure, SameSite, etc.).

Operações básicas no navegador: document.cookie

Abaixo está o exemplo mínimo para gravar um cookie no navegador. Crie um cookie anexando uma string a document.cookie.

 1// Set a simple cookie that expires in 7 days.
 2// Note: Comments are in English per the user's preference.
 3const setSimpleCookie = (name: string, value: string) => {
 4  const days = 7;
 5  const expires = new Date(Date.now() + days * 86400_000).toUTCString();
 6  // name=value; Expires=...; Path=/
 7  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; Expires=${expires}; Path=/`;
 8};
 9
10setSimpleCookie('theme', 'dark');
  • Este código cria um cookie que expira em 7 dias. É fácil de usar no lado do navegador, mas como você não pode definir HttpOnly, deve evitar armazenar informações sensíveis.

Leitura de cookies (navegador)

A função a seguir é uma função auxiliar que recupera o valor do cookie com um nome especificado a partir de document.cookie. document.cookie é retornado como uma string delimitada por ponto e vírgula e um espaço ('; '). Portanto, nós a dividimos e procuramos o cookie desejado.

 1// Parse document.cookie and return value for given name or null if not found.
 2const getCookie = (name: string): string | null => {
 3  const cookies = document.cookie ? document.cookie.split('; ') : [];
 4  for (const cookie of cookies) {
 5    const [k, ...rest] = cookie.split('=');
 6    const v = rest.join('=');
 7    if (decodeURIComponent(k) === name) {
 8      return decodeURIComponent(v);
 9    }
10  }
11  return null;
12};
13
14// Example usage:
15const theme = getCookie('theme'); // => "dark" if set
16console.log('theme cookie:', theme);
  • Esta função decodifica tanto o nome quanto o valor do cookie para compará-los e recuperá-los com segurança. Ela lida com nomes de cookies duplicados e caracteres especiais codificados, tornando-a uma implementação simples, porém robusta.

Compreendendo e aplicando atributos de segurança

Os cookies têm vários atributos importantes, cada um controlando a segurança e o escopo de seu comportamento.

  • O atributo HttpOnly impede que o cookie seja acessível a partir do JavaScript, ajudando a mitigar ataques de XSS (cross-site scripting). Observe que não é possível definir HttpOnly no lado do navegador.
  • O atributo Secure restringe o envio de cookies apenas via HTTPS, reduzindo o risco de interceptação e adulteração.
  • O atributo SameSite controla se os cookies são enviados em requisições cross-site e ajuda a prevenir ataques de CSRF (cross-site request forgery).
  • O atributo Path especifica o escopo dos caminhos de requisição para os quais o cookie é enviado, e o atributo Domain especifica o domínio no qual o cookie é válido.
  • Ao definir o atributo Expires ou Max-Age, você pode controlar a expiração do cookie.

Exemplo de adição de SameSite / Secure no navegador (o que é possível)

HttpOnly não pode ser definido no lado do cliente. Abaixo está um exemplo de como adicionar SameSite e Secure no lado do cliente. No entanto, Secure só tem efeito em páginas HTTPS.

 1// Set cookie with SameSite and Secure attributes (Secure only effective over HTTPS).
 2const setCookieWithAttributes = (name: string, value: string) => {
 3  const maxAge = 60 * 60 * 24 * 7; // 7 days in seconds
 4  // Note: HttpOnly cannot be set from JS; set it on server-side when you want to restrict JS access.
 5  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; `
 6    + `Max-Age=${maxAge}; `
 7    + `Path=/; `
 8    + `SameSite=Lax; `
 9    + `Secure`;
10};
11
12setCookieWithAttributes('session_hint', 'true');
  • SameSite=Lax é uma escolha segura e comum. Para restrições mais rígidas, use SameSite=Strict, mas navegações legítimas vindas de sites externos podem deixar de funcionar.

Definindo cookies no servidor (Set-Cookie) (Node / TypeScript)

No servidor você pode adicionar HttpOnly por meio do cabeçalho Set-Cookie, portanto o gerenciamento de sessão deve idealmente ser feito no lado do servidor. Abaixo está um exemplo usando o módulo http do Node.

 1// A minimal Node HTTP server in TypeScript that sets a secure HttpOnly cookie.
 2// This example uses built-in 'crypto' for a random session id.
 3import http from 'http';
 4import crypto from 'crypto';
 5
 6const server = http.createServer((req, res) => {
 7  if (req.url === '/login') {
 8    const sessionId = crypto.randomBytes(16).toString('hex');
 9    // Set cookie with HttpOnly, Secure, SameSite and Path
10    // Expires is optional — Max-Age preferred for relative lifetimes.
11    res.setHeader('Set-Cookie', `sid=${sessionId}; `
12      + `HttpOnly; `
13      + `Secure; `
14      + `SameSite=Strict; `
15      + `Path=/; `
16      + `Max-Age=3600`
17    );
18    res.writeHead(302, { Location: '/' });
19    res.end();
20    return;
21  }
22
23  res.writeHead(200, { 'Content-Type': 'text/plain' });
24  res.end('Hello\n');
25});
26
27server.listen(3000, () => {
28  console.log('Server running on http://localhost:3000');
29});
  • Quando você define o atributo HttpOnly no lado do servidor, o cookie se torna inacessível a partir do JavaScript, dificultando que ataques de XSS (cross-site scripting) o roubem. Além disso, ao adicionar o atributo Secure para que os cookies sejam sempre enviados via HTTPS, você pode evitar interceptação e adulteração e melhorar a segurança da comunicação.

Implementação de serialização/análise de cookies (um auxiliar do lado do servidor)

Abaixo está um utilitário simples do lado do servidor que constrói uma string de cabeçalho Set-Cookie.

 1// Cookie serialization helper for server-side use.
 2// Returns a properly formatted Set-Cookie header value.
 3type CookieOptions = {
 4  path?: string;
 5  domain?: string;
 6  maxAge?: number;
 7  expires?: Date;
 8  httpOnly?: boolean;
 9  secure?: boolean;
10  sameSite?: 'Strict' | 'Lax' | 'None';
11};
12
13const serializeCookie = (name: string, value: string, opts: CookieOptions = {}): string => {
14  const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
15
16  if (opts.maxAge != null) parts.push(`Max-Age=${Math.floor(opts.maxAge)}`);
17  if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);
18  if (opts.domain) parts.push(`Domain=${opts.domain}`);
19  parts.push(`Path=${opts.path ?? '/'}`);
20  if (opts.httpOnly) parts.push('HttpOnly');
21  if (opts.secure) parts.push('Secure');
22  if (opts.sameSite) parts.push(`SameSite=${opts.sameSite}`);
23
24  return parts.join('; ');
25};
26
27// Example usage:
28const headerValue = serializeCookie('uid', 'abc123', {
29  httpOnly: true,
30  secure: true,
31  sameSite: 'Lax',
32  maxAge: 3600
33});
34console.log(headerValue);
35// => "uid=abc123; Max-Age=3600; Path=/; HttpOnly; Secure; SameSite=Lax"
  • Esse utilitário forma a base para criar Set-Cookie em um servidor personalizado. Bibliotecas podem lidar com muitos outros casos extremos.

Prevenindo adulteração com assinaturas (HMAC)

É perigoso armazenar valores importantes diretamente em cookies. Apresentamos um método que assina valores no servidor para detectar adulteração. Aqui usamos HMAC-SHA256. Em produção, você também precisa gerenciar a revogação de tokens no lado do servidor.

 1// A simple HMAC signing and verification helper for cookies.
 2// Signing prevents client from tampering cookie values.
 3import crypto from 'crypto';
 4
 5const SECRET = 'replace_with_env_secret'; // store in env var in production
 6
 7const signValue = (value: string) => {
 8  const hmac = crypto.createHmac('sha256', SECRET);
 9  hmac.update(value);
10  return `${value}.${hmac.digest('hex')}`;
11};
12
13const verifySignedValue = (signed: string): string | null => {
14  const idx = signed.lastIndexOf('.');
15  if (idx === -1) return null;
16  const value = signed.slice(0, idx);
17  const sig = signed.slice(idx + 1);
18
19  const hmac = crypto.createHmac('sha256', SECRET);
20  hmac.update(value);
21  const expected = hmac.digest('hex');
22  // use timing-safe comparison in production
23  if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
24    return value;
25  }
26  return null;
27};
28
29// Example usage:
30const signed = signValue('userId=42');
31console.log('signed:', signed);
32console.log('verified:', verifySignedValue(signed)); // => "userId=42"
  • Com este método, você pode verificar se o valor do Cookie foi adulterado. Não inclua a chave de assinatura no código-fonte; gerencie-a por meios seguros, como variáveis de ambiente.

Cookies e mitigação de CSRF (padrões gerais)

Sessões que usam cookies precisam de proteção contra CSRF. Mitigações típicas incluem:.

  • Defina SameSite como Lax ou Strict para evitar envio entre sites desnecessário. Use SameSite=None; Secure apenas quando a comunicação entre origens (cross-origin) for necessária.
  • Use tokens CSRF, validados pelo servidor, para formulários e requisições de API. Não armazene o token em um cookie HttpOnly; em vez disso, forneça-o pelo corpo da resposta ou por meta tags, e faça com que o JavaScript o leia apenas quando necessário.
  • Se o token puder ser acessado pelo JavaScript, certifique-se de também implementar proteções contra XSS (CSP, escape de saída, etc.).
  • Exija a reautenticação para ações sensíveis e no login, e previna ataques de fixação de sessão.

Abaixo está um exemplo de cliente seguro que envia o token CSRF em um cabeçalho. Isso pressupõe que o cliente já tenha recebido o token do servidor, por exemplo, no corpo da resposta.

 1// Example of sending CSRF token safely in a header using fetch.
 2// Assumes CSRF token was provided securely (e.g., via response body or meta tag).
 3async function sendProtectedRequest(url: string, csrfToken: string) {
 4  const res = await fetch(url, {
 5    method: 'POST',
 6    credentials: 'same-origin', // include cookies for same-site requests
 7    headers: {
 8      'Content-Type': 'application/json',
 9      'X-CSRF-Token': csrfToken
10    },
11    body: JSON.stringify({ action: 'doSomething' })
12  });
13  return res;
14}
  • Se você especificar credentials: 'same-origin', apenas os cookies da mesma origem são enviados. No servidor, valide o valor do cabeçalho X-CSRF-Token e verifique se o token corresponde. Se solicitações entre sites forem necessárias, configure o CORS com cuidado.

Entre sites (CORS) e a relação com credentials

Para enviar e receber cookies em comunicação entre origens, o cliente deve especificar credentials: 'include' e o servidor deve definir Access-Control-Allow-Credentials: true. No entanto, restrinja essa configuração a origens confiáveis e não use Access-Control-Allow-Origin: *.

 1async function fetchCrossOriginData() {
 2  // Example: cross-origin fetch sending cookies.
 3  // Server must set Access-Control-Allow-Credentials: true
 4  // and a specific trusted origin.
 5  const res = await fetch('https://api.example.com/data', {
 6    credentials: 'include', // send cookies only to trusted domains
 7    method: 'GET'
 8  });
 9  return res;
10}
  • A combinação de CORS e cookies é muito delicada do ponto de vista de segurança. Gerencie as origens permitidas de forma rígida com uma lista de permissões e evite comunicação entre sites desnecessária. Além disso, ao usar SameSite=None; Secure, imponha o uso de HTTPS para prevenir ataques man-in-the-middle.

Exemplo: configurações seguras de cookies de sessão com Express (TypeScript)

Na prática, use Express, a biblioteca cookie, express-session e assim por diante. Abaixo está um exemplo simples usando express e cookie-parser. Na prática, defina secure como verdadeiro e gerencie o secret por meio de variáveis de ambiente.

 1// Express example using cookie-parser and setting a secure httpOnly cookie.
 2// npm install express cookie-parser @types/express @types/cookie-parser
 3import express from 'express';
 4import cookieParser from 'cookie-parser';
 5
 6const app = express();
 7app.use(cookieParser(process.env.COOKIE_SECRET));
 8
 9app.post('/login', (req, res) => {
10  // authenticate user (omitted)
11  const sessionId = 'generated-session-id';
12  res.cookie('sid', sessionId, {
13    httpOnly: true,
14    secure: true,        // require HTTPS in production
15    sameSite: 'lax',
16    maxAge: 1000 * 60 * 60 // 1 hour
17  });
18  res.json({ ok: true });
19});
20
21app.listen(3000);
  • Ao usar o recurso secret do cookieParser, você pode trabalhar facilmente com cookies assinados. No entanto, em aplicações reais, do ponto de vista da segurança e da escalabilidade, você não deve armazenar dados diretamente em cookies; em vez disso, use um armazenamento de sessão dedicado.

Prefixos de cookies __Host- e __Secure-

Os navegadores impõem regras especiais para certos prefixos.

  • O prefixo __Secure- Se o nome de um cookie começa com __Secure-, o atributo Secure é obrigatório.
  • O prefixo __Host- Se começar com __Host-, Secure é obrigatório, o caminho deve ser / (raiz) e Domain não deve ser definido.

Usá-los reduz configurações incorretas e melhora a segurança.

Melhores práticas para cookies

Para lidar com Cookies com segurança, considere os seguintes pontos.

  • Não armazene informações sensíveis diretamente em cookies. Prefira um ID de sessão no servidor para tokens de acesso.
  • Defina cookies de sessão com HttpOnly, Secure e SameSite=Lax (ou Strict).
  • Aproveite prefixos como __Host- e __Secure-.
  • Considere assinaturas (HMAC) e criptografia para evitar adulteração e interceptação.
  • Habilite Secure e exija HTTPS.
  • Busque o princípio do menor privilégio e expirações curtas.
  • Use tokens CSRF.
  • Cuidado com as diferenças no comportamento de SameSite entre navegadores, especialmente os mais antigos.

Equívocos comuns sobre cookies

Em relação aos cookies, atente para os seguintes equívocos comuns.

  • 'Adicionar HttpOnly elimina o impacto de XSS.' Embora HttpOnly impeça que os cookies sejam acessados pelo JavaScript, o XSS ainda pode ser usado para emitir requisições arbitrárias. Você também deve empregar verificação de token CSRF, CSP (Content Security Policy) e sanitização de entrada.
  • 'Secure é desnecessário para desenvolvimento local.' Simular HTTPS e verificar o comportamento mesmo em ambientes locais melhora a precisão dos testes. No mínimo, HTTPS deve ser usado em ambientes de homologação e produção.
  • 'Prazos de expiração longos são convenientes.' Se você definir tempos de vida longos para os cookies, o período durante o qual eles podem ser abusados, caso sejam roubados, aumenta. Você pode definir expirações mais curtas e incorporar reautenticação periódica e rotação de tokens.

Resumo

Embora os cookies sejam fáceis de usar, tratá-los de forma inadequada pode introduzir vulnerabilidades de segurança. Para gerenciá-los adequadamente com TypeScript, é importante entender atributos como HttpOnly, Secure e SameSite, e aplicar configurações seguras no lado do servidor. Ao não armazenar dados sensíveis diretamente e combinar assinaturas com expirações curtas, você pode alcançar um gerenciamento de sessão seguro e confiável.

Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.

YouTube Video