Domínio do cookie: login que some entre www e apex ou vaza para subdomínio
Entenda quando o cookie vale só para o host e quando vale para todo subdomínio, escolha o escopo certo da sessão e use o prefixo __Host- para travar.
A pessoa entra em www.lojamodelo.com.br, faz login, clica num link de e-mail que aponta para lojamodelo.com.br e aparece deslogada. Alguém "conserta" colocando Domain=lojamodelo.com.br no cookie de sessão, e o login passa a funcionar nos dois. Também passa a ser enviado para blog.lojamodelo.com.br, hospedado num serviço de terceiros, e para homolog.lojamodelo.com.br, que roda uma versão de seis meses atrás. A resposta curta: sem o atributo Domain, o cookie volta só para o host exato que o criou; com Domain, ele vai para aquele domínio e todos os subdomínios. Para sessão, deixe o cookie restrito ao host, escolha um único endereço oficial para o site e redirecione o outro para ele.
O prefixo __Host- no nome do cookie trava essa decisão: o navegador só aceita o cookie se ele for Secure, tiver Path=/ e não tiver Domain.
Como o navegador decide para onde o cookie vai
Quando o servidor manda Set-Cookie, o navegador guarda o cookie junto com um escopo. Em cada pedido seguinte, ele confere se o endereço de destino cabe nesse escopo antes de anexar o cookie. Dois atributos definem o escopo de endereço:
Domainausente: cookie host-only. Vale só para o host exato da resposta. Criado emwww.lojamodelo.com.br, não vai paralojamodelo.com.brnem paraapi.lojamodelo.com.br.Domain=lojamodelo.com.br: vale para esse domínio e para qualquer subdomínio dele, em qualquer profundidade. Um ponto no começo do valor é ignorado pelos navegadores atuais; não muda nada.Path: restringe pelo caminho da URL. Não é fronteira de segurança, porque páginas do mesmo host conseguem ler cookies de outros caminhos por outros meios; serve para organizar, não para isolar.
Há limites que o navegador impõe. Um site não consegue definir Domain para outro domínio que não seja o dele ou um pai dele, e não consegue definir para um sufixo público como com.br. A lista de sufixos públicos (Public Suffix List) é o que impede lojamodelo.com.br de criar cookie para todo .com.br.
O lado que costuma ser esquecido: subdomínio escrevendo cookie
A regra vale nos dois sentidos. Qualquer página em blog.lojamodelo.com.br pode emitir um cookie com Domain=lojamodelo.com.br, e esse cookie chega à loja principal. Se o subdomínio for comprometido ou estiver sob controle de terceiros, ele pode plantar um cookie com o mesmo nome da sua sessão e tentar fixar uma sessão conhecida. O prefixo __Host- fecha essa porta, porque um cookie com esse prefixo nunca pode ter Domain, então nenhum subdomínio consegue criá-lo para o domínio pai.
Vale lembrar que SameSite não ajuda aqui. Para o navegador, todos os subdomínios do mesmo domínio registrável são o mesmo site, então SameSite=Strict ainda envia o cookie entre blog e www. Os atributos HttpOnly, Secure e SameSite estão detalhados em cookie de sessão com HttpOnly, Secure e SameSite; este post trata do escopo de domínio.
Como escolher o escopo da sessão
- Liste todos os hosts do seu domínio: site, API, painel, blog, homologação, ferramentas de marketing, subdomínios antigos que ainda resolvem no DNS.
- Escolha o endereço oficial do site, com ou sem
www, e redirecione o outro com 301 para ele, preservando caminho e query. - Emita o cookie de sessão sem
Domaine com o prefixo__Host-,Secure,HttpOnlyePath=/. - Se a API está em outro subdomínio, prefira servi-la no mesmo host, num caminho como
/api, a ampliar o cookie para o domínio inteiro. - Se precisar mesmo compartilhar entre subdomínios, use um domínio separado só para hosts que você controla, como
app-lojamodelo.com.br, em vez de abrir o domínio onde vivem blog e ferramentas de terceiros. - Teste nos dois nomes e num subdomínio que não deveria receber o cookie.
O cookie de sessão no Express
app.post('/login', async (req, res) => {
const sessao = await criarSessao(req.body.email, req.body.senha);
if (!sessao) return res.status(401).send('Credenciais inválidas');
res.cookie('__Host-sessao', sessao.id, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 8 * 60 * 60 * 1000,
});
res.redirect('/conta');
});
Não há opção domain no objeto, então o cookie é host-only. O cabeçalho que sai fica parecido com este:
Set-Cookie: __Host-sessao=k3f9...; Max-Age=28800; Path=/; Expires=...; HttpOnly; Secure; SameSite=Lax
Se alguém adicionar domain no futuro, o navegador rejeita o cookie por causa do prefixo, e o login quebra em teste em vez de vazar em produção. É um erro barulhento de propósito.
O redirecionamento para um único endereço
No nginx, o host que não é oficial só redireciona:
server {
listen 443 ssl;
server_name lojamodelo.com.br;
ssl_certificate /etc/letsencrypt/live/lojamodelo.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lojamodelo.com.br/privkey.pem;
return 301 https://www.lojamodelo.com.br$request_uri;
}
O certificado precisa cobrir os dois nomes, senão o navegador mostra erro antes do redirecionamento acontecer. Os detalhes de código de status e de como não perder caminho e parâmetros estão em redirecionar URL do jeito certo.
Como conferir o que o navegador guardou
No Chrome ou no Edge, abra as ferramentas de desenvolvedor, aba Application, seção Cookies. A coluna Domain mostra o host exato quando o cookie é host-only, e o domínio, às vezes com um ponto na frente, quando foi definido com Domain. No Firefox, a aba equivalente é Storage. Pelo terminal, curl -sI num endpoint que emite cookie mostra o Set-Cookie cru, e basta procurar Domain= nele.
Depois, visite um subdomínio que não deveria receber a sessão e confira, na aba Network, se o cabeçalho Cookie do pedido traz o cookie. Esse teste costuma revelar o subdomínio esquecido.
Erros comuns
- Usar
Domainpara resolver o problema www versus apex. O login passa a funcionar nos dois e a sessão vai junto para todo subdomínio, inclusive os de terceiros. - Manter os dois endereços servindo o site. Cada um tem seus cookies, a pessoa parece deslogada ao trocar de nome e a busca indexa duas cópias.
- Esquecer subdomínios antigos no DNS. Um host abandonado que aponta para serviço de terceiros pode ser tomado e passar a receber cookies do domínio pai.
- Achar que
SameSiteisola subdomínios. Subdomínios do mesmo domínio registrável são o mesmo site, e o cookie é enviado normalmente. - Usar
Pathcomo isolamento entre aplicações. Duas aplicações no mesmo host conseguem acessar os cookies uma da outra. - Homologação no mesmo domínio da produção com cookie amplo. A versão de teste recebe a sessão real de quem visitou os dois.
Checklist
- O cookie de sessão não tem atributo
Domain. - O nome do cookie de sessão usa o prefixo
__Host-. - Existe um único endereço oficial e o outro redireciona com 301.
- O certificado cobre os dois nomes.
- A lista de subdomínios do DNS foi revisada e os abandonados removidos.
- Homologação e ferramentas de terceiros não ficam sob um domínio que recebe a sessão.
- Um teste manual confirma que um subdomínio qualquer não recebe o cookie.
Abra a aba de cookies no site de produção agora: a sessão aparece com o host exato ou com o domínio inteiro, e quantos subdomínios no seu DNS a receberiam hoje?
Referência: https://developer.mozilla.org/docs/Web/HTTP/Cookies#define_where_cookies_are_sent