---
title: "Cookies e Sessões em Zig: Login HTTP Seguro sem Framework"
url: "https://ziglang.com.br/artigos/zig-cookies-sessao-http/"
markdown_url: "https://ziglang.com.br/artigos/zig-cookies-sessao-http.MD"
description: "Como implementar cookies e sessões em uma API Zig: Set-Cookie, parsing de Cookie, Secure, HttpOnly, SameSite, CSRF, expiração, rotação e testes."
date: "2026-08-15"
author: ""
---

# Cookies e Sessões em Zig: Login HTTP Seguro sem Framework

Como implementar cookies e sessões em uma API Zig: Set-Cookie, parsing de Cookie, Secure, HttpOnly, SameSite, CSRF, expiração, rotação e testes.


Para implementar **cookies e sessões em uma API Zig** com segurança, envie ao navegador apenas um identificador de sessão aleatório e mantenha os dados confiáveis no servidor. O cookie deve usar `HttpOnly`, `Secure`, `SameSite` e `Path`; o backend deve armazenar somente um hash do identificador, aplicar expiração, rotacionar a sessão depois do login e revogá-la no logout.

A regra central é: **o cookie não é a sessão; ele é uma credencial que aponta para a sessão**. Trate seu valor como uma senha. Não coloque email, função, permissões ou JSON sensível em texto puro e não registre o identificador em logs. Como a API de `std.http.Server` ainda pode mudar antes do Zig 1.0, mantenha parsing, política e armazenamento separados do adaptador HTTP.

## Resposta rápida

| Decisão | Recomendação inicial |
|---|---|
| Conteúdo do cookie | identificador aleatório opaco |
| Nome | `__Host-session` quando os requisitos puderem ser cumpridos |
| Flags | `HttpOnly; Secure; SameSite=Lax; Path=/` |
| Armazenamento | hash do identificador + usuário + expiração + metadados mínimos |
| Expiração | absoluta e, se necessário, por inatividade |
| Depois do login | rotacionar o identificador para evitar session fixation |
| Logout | revogar no servidor e expirar o cookie no navegador |
| Operações mutáveis | proteção CSRF além de `HttpOnly` |
| Logs | nunca gravar o cookie ou o identificador completo |

Se sua API usa tokens Bearer em vez de cookies, leia o guia de [JWT e autenticação em Zig](/artigos/zig-jwt-autenticacao-api/). Para configurar acesso de um frontend em outro domínio, veja [CORS em APIs Zig](/artigos/zig-cors-api-http/). Cookies, CORS e CSRF se relacionam, mas resolvem problemas diferentes.

## Cookie e sessão não são a mesma coisa

Um cookie é um pequeno valor que o servidor envia no header `Set-Cookie`. Em requisições futuras compatíveis com domínio, caminho e política de segurança, o navegador devolve esse valor no header `Cookie`.

Uma sessão server-side é um registro controlado pela aplicação. Ela pode conter:

- identificador interno do usuário;
- instante de criação e expiração;
- versão ou nível de autenticação;
- escopos calculados pelo servidor;
- estado de revogação;
- metadados mínimos para auditoria.

O navegador não precisa receber esse registro. Ele recebe apenas uma credencial opaca, por exemplo:

```http
Set-Cookie: __Host-session=4Jm...valor-aleatorio...9Qc; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600
```

Depois, envia:

```http
Cookie: __Host-session=4Jm...valor-aleatorio...9Qc
```

O backend aplica um hash ao valor recebido, procura esse hash no armazenamento e verifica se a sessão existe, não expirou e não foi revogada. Só então cria um contexto autenticado para o handler.

## Modelo de dados explícito em Zig

Uma sessão pode ser representada sem acoplar o domínio ao banco ou ao servidor HTTP:

```zig
const Session = struct {
    user_id: u64,
    created_at: i64,
    expires_at: i64,
    last_seen_at: i64,
    auth_level: AuthLevel,
    revoked: bool,
};

const AuthLevel = enum {
    password,
    mfa,
};

const SessionError = error{
    Missing,
    Malformed,
    NotFound,
    Expired,
    Revoked,
};
```

A camada HTTP extrai uma slice do header. A camada de sessão valida o formato, calcula o hash e consulta um `SessionStore`. O handler recebe um `AuthContext` já confiável:

```zig
const AuthContext = struct {
    user_id: u64,
    auth_level: AuthLevel,
};

fn authenticate(
    store: *SessionStore,
    raw_cookie_header: ?[]const u8,
    now: i64,
) SessionError!AuthContext {
    const token = try findCookie(raw_cookie_header orelse return error.Missing, "__Host-session");
    try validateSessionToken(token);

    const token_hash = hashSessionToken(token);
    const session = try store.get(token_hash);

    if (session.revoked) return error.Revoked;
    if (session.expires_at <= now) return error.Expired;

    return .{
        .user_id = session.user_id,
        .auth_level = session.auth_level,
    };
}
```

Os nomes de `SessionStore`, `findCookie` e `hashSessionToken` representam componentes da aplicação. Essa separação é intencional: somente o adaptador que lê headers precisa acompanhar mudanças de `std.http`.

## Gere um identificador imprevisível

Um contador, timestamp, UUID previsível ou hash do email não serve como identificador de sessão. Quem adivinhar o valor assume a conta do usuário.

Use o gerador criptográfico disponibilizado pela versão instalada do Zig e produza pelo menos 32 bytes aleatórios. Depois, codifique em Base64 URL-safe sem padding ou em hexadecimal. O resultado precisa:

- ter entropia criptográfica suficiente;
- usar um alfabeto aceito no valor do cookie;
- ter tamanho máximo pequeno e validado;
- não carregar significado de negócio;
- nunca ser reutilizado entre usuários.

Esqueleto conceitual:

```zig
fn createSessionToken(allocator: std.mem.Allocator) ![]u8 {
    var random_bytes: [32]u8 = undefined;
    std.crypto.random.bytes(&random_bytes);

    return encodeBase64UrlNoPadding(allocator, &random_bytes);
}
```

Confira a API de codificação do seu release. O ponto de segurança não depende do nome exato da função: bytes vêm de uma fonte criptográfica e são codificados sem reduzir sua aleatoriedade.

## Salve o hash, não a credencial original

Se o banco de sessões for lido indevidamente e armazenar os tokens em texto puro, cada registro ativo poderá ser usado imediatamente. Um desenho melhor guarda uma derivação unilateral:

```text
token entregue ao navegador
        ↓ hash criptográfico
chave usada no SessionStore
```

Para um token aleatório de alta entropia, um hash criptográfico como SHA-256 é adequado para indexação; ele não precisa do custo de uma função lenta para senhas. Compare chaves em tempo constante quando a arquitetura expuser comparação direta e não registre nem token nem hash completo.

O banco pode usar o hash como chave primária e manter o restante do registro separado. Ao receber o cookie, o servidor calcula o mesmo hash e faz a busca. No logout, remove ou marca esse registro como revogado.

## Monte `Set-Cookie` de forma centralizada

Não espalhe concatenação de strings pelos handlers. Uma função única reduz erros de atributos e facilita revisar a política:

```zig
const CookiePolicy = struct {
    name: []const u8 = "__Host-session",
    path: []const u8 = "/",
    max_age_seconds: u32 = 3600,
    secure: bool = true,
    http_only: bool = true,
    same_site: SameSite = .lax,
};

const SameSite = enum {
    strict,
    lax,
    none,
};
```

A serialização deve rejeitar caracteres de controle, ponto e vírgula e qualquer valor fora do alfabeto escolhido. Não aceite um nome, domínio ou caminho vindo diretamente da requisição.

Uma resposta de login típica inclui:

```http
HTTP/1.1 204 No Content
Set-Cookie: __Host-session=TOKEN; Path=/; Max-Age=3600; Secure; HttpOnly; SameSite=Lax
Cache-Control: no-store
```

O prefixo `__Host-` adiciona restrições úteis nos navegadores compatíveis: o cookie deve ter `Secure`, usar `Path=/` e não pode declarar `Domain`. Isso evita que um subdomínio defina um cookie de sessão mais amplo em nome do domínio principal. Se sua arquitetura realmente exige `Domain`, não poderá usar esse prefixo.

## O que cada atributo protege

### HttpOnly

`HttpOnly` impede que JavaScript acesse o cookie por APIs como `document.cookie`. Isso reduz o impacto de certas falhas de XSS, mas não torna uma página vulnerável segura: um script malicioso ainda pode fazer requisições autenticadas dentro da origem.

### Secure

`Secure` faz o navegador enviar o cookie apenas por HTTPS. Em produção, deve ser obrigatório. Termine TLS no proxy ou gateway e garanta que a aplicação não publique um fluxo de login acessível em HTTP. O guia de [TLS, HTTPS e mTLS em Zig](/artigos/zig-tls-https-certificados-mtls/) cobre a fronteira de transporte.

### SameSite

`SameSite` controla quando o navegador envia o cookie em contextos entre sites:

- `Strict`: isolamento maior, mas pode quebrar navegação legítima iniciada em outro site;
- `Lax`: bom padrão inicial para muitos aplicativos, permitindo alguns fluxos de navegação de topo;
- `None`: necessário em certos cenários realmente cross-site; exige `Secure`.

Não escolha `None` apenas porque o frontend está em outro subdomínio. “Site” e “origin” são conceitos diferentes, e a arquitetura deve ser analisada com domínio, esquema e fluxo de autenticação reais.

### Path, Domain e Max-Age

Use o menor escopo necessário. `Path=/` é exigido por `__Host-` e costuma ser adequado para sessão geral. Evite `Domain` se apenas o host atual precisa do cookie. Prefira `Max-Age` para declarar a duração e, se também emitir `Expires`, mantenha os dois coerentes.

## Parsing do header Cookie

O header recebido contém pares separados por ponto e vírgula:

```http
Cookie: tema=escuro; __Host-session=abc123; idioma=pt-BR
```

O parser deve encontrar o nome exato, ignorar espaços opcionais ao redor dos pares e impor limites. Não use busca por substring, porque `session_backup` não é `session`.

```zig
fn findCookie(header: []const u8, wanted: []const u8) ![]const u8 {
    if (header.len > 8192) return error.Malformed;

    var pairs = std.mem.splitScalar(u8, header, ';');
    while (pairs.next()) |raw_pair| {
        const pair = std.mem.trim(u8, raw_pair, " \t");
        const equal = std.mem.indexOfScalar(u8, pair, '=') orelse continue;

        const name = std.mem.trim(u8, pair[0..equal], " \t");
        const value = pair[equal + 1 ..];

        if (std.mem.eql(u8, name, wanted)) return value;
    }

    return error.Missing;
}
```

Esse exemplo é propositalmente restrito. Antes de usá-lo como parser genérico, defina como tratar valores vazios, múltiplos headers, cookies duplicados, caracteres não aceitos e o tamanho máximo do token. Para uma credencial própria com alfabeto controlado, valide exatamente esse alfabeto depois da extração.

Cookies duplicados merecem política explícita. Não aceite silenciosamente “o primeiro” em um componente e “o último” em outro, pois diferenças entre proxy e aplicação podem criar ambiguidades. Para o cookie de sessão, uma opção conservadora é rejeitar a requisição quando houver mais de um valor com o mesmo nome.

## Fluxo de login sem session fixation

Session fixation ocorre quando a aplicação autentica uma sessão cujo identificador já era conhecido antes do login. O atacante tenta fazer a vítima usar esse identificador e espera que ele ganhe privilégios.

O fluxo seguro é:

1. validar credenciais;
2. revogar qualquer sessão anônima que não precise continuar;
3. gerar um identificador completamente novo;
4. criar o registro autenticado no armazenamento;
5. emitir o novo `Set-Cookie`;
6. evitar cache da resposta;
7. registrar apenas o evento e um ID de auditoria seguro.

Rotacione novamente depois de mudanças sensíveis, como conclusão de MFA, troca de senha, recuperação de conta ou elevação de privilégio. Nunca apenas altere `user_id` no registro apontado pelo token antigo.

## Expiração absoluta e por inatividade

Uma sessão eterna transforma qualquer vazamento antigo em risco permanente. Use pelo menos uma expiração absoluta. Aplicações sensíveis podem combinar:

- **expiração absoluta**: a sessão termina após um teto, mesmo com uso contínuo;
- **expiração por inatividade**: termina depois de um período sem atividade;
- **reauthentication**: ações críticas exigem senha ou MFA recente.

Evite gravar `last_seen_at` em todas as requisições, pois isso aumenta contenção e custo de armazenamento. Atualize em intervalos, por exemplo quando o valor estiver mais velho que alguns minutos. A decisão precisa ser atômica o suficiente para não ressuscitar uma sessão revogada.

Limpeza periódica também é necessária. Um job pode remover sessões vencidas em lotes sem bloquear o caminho de login. Veja [cron jobs em Zig para produção](/artigos/zig-cron-jobs-producao/) para limites, locks e observabilidade desse processo.

## Logout precisa agir nos dois lados

Apagar o cookie no navegador sem revogar o registro deixa a credencial válida se ela tiver sido copiada. Revogar apenas no servidor deixa um cookie inútil sendo reenviado.

Faça ambos:

```http
Set-Cookie: __Host-session=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Lax
Cache-Control: no-store
```

No servidor, remova ou marque a sessão como revogada. Use os mesmos atributos relevantes, especialmente nome e `Path`, ao expirar o cookie. Se a aplicação mantém várias sessões por usuário, ofereça também “sair de todos os dispositivos”, revogando todos os registros associados à conta.

## HttpOnly não resolve CSRF

Com autenticação por cookie, o navegador anexa a credencial automaticamente. Um site malicioso pode tentar induzir o navegador a enviar uma operação para sua aplicação. `HttpOnly` não impede isso.

Uma defesa em camadas inclui:

1. `SameSite=Lax` ou `Strict` quando compatível com o produto;
2. métodos seguros (`GET` não deve alterar estado);
3. validação de `Origin` em operações mutáveis quando o header estiver presente no fluxo esperado;
4. token CSRF imprevisível, associado à sessão e exigido no body ou em header customizado;
5. CORS restrito quando um frontend em outra origem precisa ler respostas;
6. reautenticação para ações críticas.

Não use o cookie de sessão como token CSRF. Um padrão comum é enviar um token separado na página ou em endpoint próprio e exigir que o frontend o devolva em `X-CSRF-Token`. A comparação deve ser segura, e o token precisa estar ligado à sessão ou a uma estratégia equivalente documentada.

## CORS com cookie exige configuração coordenada

Quando frontend e API usam origens diferentes, o navegador exige uma política CORS compatível. Em geral:

- a resposta precisa usar uma origem específica, não `*`;
- `Access-Control-Allow-Credentials: true` deve ser emitido quando necessário;
- o JavaScript precisa optar pelo envio de credenciais;
- o cookie precisa de atributos compatíveis com o contexto;
- preflight e resposta real devem concordar.

Mesmo com CORS correto, CSRF continua sendo uma decisão própria. CORS controla se o JavaScript de outra origem pode ler a resposta; ele não é um sistema geral de autenticação.

## Onde armazenar sessões

A interface pode começar pequena:

```zig
const SessionStore = struct {
    pub fn put(self: *SessionStore, hash: [32]u8, session: Session) !void {
        // adaptador: memória, SQLite, PostgreSQL ou Redis
    }

    pub fn get(self: *SessionStore, hash: [32]u8) !Session {
        // deve distinguir ausente de falha operacional
    }

    pub fn revoke(self: *SessionStore, hash: [32]u8) !void {
        // revogação idempotente
    }
};
```

Escolha conforme a implantação:

| Cenário | Armazenamento possível | Limite principal |
|---|---|---|
| ferramenta local ou protótipo único | memória | perde tudo no restart e não escala entre processos |
| serviço em uma máquina | SQLite | precisa de política de concorrência e backup |
| aplicação com banco existente | PostgreSQL | custo por request e limpeza de registros |
| várias instâncias com sessões rápidas | Redis | disponibilidade, TTL e persistência precisam ser definidos |

Não esconda falha do armazenamento como “usuário deslogado”. Se o Redis ou Postgres está indisponível, devolver `401` pode confundir incidente operacional com credencial inválida. Dependendo da rota, responda com erro temporário e registre a causa sem expor detalhes. O guia de [bancos de dados em Zig](/artigos/zig-banco-dados-integracoes/) ajuda a comparar essas opções.

## Limites e observabilidade

A autenticação fica na borda de todas as rotas protegidas, por isso precisa falhar de forma barata e mensurável.

Defina limites para:

- tamanho total dos headers;
- tamanho do valor da sessão;
- alfabeto e comprimento após parsing;
- número de cookies aceitos;
- quantidade de sessões ativas por usuário;
- tentativas de login e criação de sessão.

Registre eventos como `session_created`, `session_rotated`, `session_expired` e `session_revoked`. Inclua um request ID e um identificador interno de auditoria, mas não o cookie. Métricas úteis incluem taxa de sessões inválidas, expirações, falhas no store e latência da consulta.

Não use o hash completo como label de Prometheus: além de sensível, isso cria cardinalidade sem limite. Para limitar abuso no login, combine com [rate limiting em Zig](/artigos/zig-rate-limiting-token-bucket/) e logs estruturados do guia de [observabilidade](/artigos/zig-observabilidade-logs-prometheus/).

## Testes com curl

Um cookie jar permite testar o fluxo sem copiar a credencial manualmente.

Faça login e salve os cookies:

```bash
curl -i -c cookies.txt \
  -H 'Content-Type: application/json' \
  --data '{"email":"ana@example.com","senha":"..."}' \
  https://api.example.com/login
```

Use a sessão:

```bash
curl -i -b cookies.txt https://api.example.com/minha-conta
```

Faça logout:

```bash
curl -i -b cookies.txt -c cookies.txt \
  -X POST https://api.example.com/logout
```

No ambiente de teste, verifique:

- presença de `Secure`, `HttpOnly`, `SameSite` e `Path`;
- ausência de `Domain` quando usar `__Host-`;
- rotação do valor após login e MFA;
- rejeição depois da expiração;
- revogação real depois do logout;
- comportamento de cookie ausente, truncado e grande demais;
- cookie duplicado com valores diferentes;
- falha do armazenamento de sessão;
- proteção CSRF em `POST`, `PUT`, `PATCH` e `DELETE`;
- resposta com `Cache-Control: no-store` nos fluxos sensíveis.

Um teste de integração deve controlar o relógio ou injetar `now`, em vez de esperar uma sessão expirar de verdade. Isso torna casos de fronteira determinísticos.

## Erros comuns

### Colocar permissões no cookie em texto puro

O cliente controla o valor recebido e pode alterá-lo. Mesmo um cookie assinado exige política cuidadosa de expiração e revogação. Para uma sessão opaca, mantenha autorização no servidor.

### Gerar token com timestamp ou contador

Identificadores previsíveis permitem sequestro de sessão. Use aleatoriedade criptográfica.

### Armazenar token puro no banco

Um vazamento do store vira acesso imediato às contas ativas. Armazene o hash.

### Esquecer rotação depois do login

Isso abre espaço para session fixation. Gere outra credencial ao mudar o nível de confiança.

### Confiar apenas em HttpOnly

Ele reduz leitura por JavaScript, mas não resolve CSRF, XSS, autorização nem transporte sem TLS.

### Usar SameSite=None sem necessidade

Isso amplia o envio cross-site e exige uma estratégia CSRF compatível. Comece com o valor mais restritivo que preserve o produto.

### Fazer logout somente no navegador

A credencial copiada continua válida. Revogue no servidor.

### Registrar o header Cookie

Access logs, traces e mensagens de debug podem transformar observabilidade em vazamento de credenciais. Faça redaction na borda.

## Checklist para produção

- [ ] O cookie contém apenas um identificador opaco e imprevisível.
- [ ] O identificador tem pelo menos 32 bytes aleatórios antes da codificação.
- [ ] O servidor armazena o hash, não o token original.
- [ ] `Secure`, `HttpOnly`, `SameSite` e `Path` estão definidos.
- [ ] `__Host-` é usado quando a arquitetura permite.
- [ ] O login e a elevação de privilégio rotacionam a sessão.
- [ ] Existe expiração absoluta e política de inatividade documentada.
- [ ] Logout revoga no store e expira o cookie.
- [ ] Operações mutáveis possuem defesa CSRF.
- [ ] O parser limita tamanho, formato e duplicidade.
- [ ] Falha do store não é mascarada automaticamente como `401`.
- [ ] Cookies e tokens nunca aparecem em logs, métricas ou traces.
- [ ] Testes cobrem expiração, revogação, rotação e atributos.
- [ ] A API HTTP específica do release está isolada em um adaptador.

## Perguntas frequentes

### Como criar uma sessão HTTP segura em Zig?

Gere um identificador aleatório com entropia criptográfica, salve somente o hash desse identificador no servidor e envie o valor original em um cookie com `HttpOnly`, `Secure`, `SameSite` e `Path` adequados. Defina expiração, rotação após login e revogação no logout.

### Devo guardar dados do usuário dentro do cookie?

Para uma sessão opaca, não. Guarde no cookie apenas um identificador imprevisível e mantenha usuário, permissões e expiração no armazenamento do servidor. Isso simplifica revogação e evita expor dados ao navegador.

### HttpOnly impede ataques CSRF?

Não. `HttpOnly` impede que JavaScript leia o cookie, mas o navegador ainda pode enviá-lo automaticamente. Use `SameSite`, valide `Origin` quando aplicável e adote token CSRF em operações mutáveis que precisam aceitar requisições entre sites.

### Qual valor de SameSite usar no cookie de sessão?

`Lax` é um padrão razoável para muitos sites. `Strict` oferece isolamento maior, mas pode interromper fluxos legítimos vindos de links externos. `None` só deve ser usado quando o envio entre sites é necessário e exige `Secure`.

### Como testar cookies em uma API Zig?

Use `curl` com um cookie jar: salve `Set-Cookie` com `-c`, reenvie com `-b` e inspecione atributos, expiração, logout e respostas sem sessão. Automatize também testes de rotação, CSRF, cookies duplicados e identificadores inválidos.

## Conclusão

Sessões HTTP seguras em Zig não exigem um framework grande, mas exigem fronteiras claras. Gere uma credencial aleatória, armazene seu hash, centralize `Set-Cookie`, valide o header com limites e transforme a sessão em um contexto autenticado antes de executar o handler.

O desenho robusto combina bem com a linguagem: tipos explícitos para estados, erros distintos para ausência e indisponibilidade, relógio injetável para testes e adaptadores pequenos para `std.http`, banco e cache. Continue pelo guia de [servidor HTTP Zig em produção](/artigos/zig-http-server-producao/), conecte a autenticação à [API REST completa](/artigos/zig-api-rest-completa/) e revise configuração segura de segredos antes do deploy.
