Maneira correta de excluir cookies do lado do servidor

141

Para o meu processo de autenticação, crio um token exclusivo quando um usuário efetua login e o coloco em um cookie usado para autenticação.

Então, eu enviava algo assim do servidor:

Set-Cookie: token=$2a$12$T94df7ArHkpkX7RGYndcq.fKU.oRlkVLOkCBNrMilaSWnTcWtCfJC; path=/;

O que funciona em todos os navegadores. Em seguida, para excluir um cookie, envio um cookie semelhante com o expirescampo definido para 1º de janeiro de 1970

Set-Cookie: token=$2a$12$T94df7ArHkpkX7RGYndcq.fKU.oRlkVLOkCBNrMilaSWnTcWtCfJC; path=/; expires=Thu, Jan 01 1970 00:00:00 UTC; 

E isso funciona bem no Firefox, mas não exclui o cookie no IE ou Safari.

Então, qual é a melhor maneira de excluir um cookie (sem JavaScript de preferência)? O método definir como expirar no passado parece volumoso. E também por que isso funciona no FF, mas não no IE ou no Safari?

Joshkunz
fonte
Veja também stackoverflow.com/a/20320610/212378
Alexis Wilke 16/17

Respostas:

208

Enviar o mesmo valor de cookie com ; expiresanexado não destruirá o cookie.

Invalide o cookie definindo um valor vazio e inclua também um expirescampo:

Set-Cookie: token=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT

Observe que você não pode forçar todos os navegadores a excluir um cookie. O cliente pode configurar o navegador de forma que o cookie persista, mesmo que tenha expirado. Definir o valor conforme descrito acima resolveria esse problema.

Lekensteyn
fonte
52
Eu recomendaria usar um texto vazio como lixo, em vez de "deleted", à confusão evitar mais tarde, com um valor potencialmente legal equivale a "excluído"
yegor256
8
@raulk Sim, você está correto. Engraçado que isso não tenha sido notado antes, espero que não tenha causado muitos problemas. yegor256, um valor vazio deve funcionar na maioria dos casos. Relacionado: algumas pessoas podem se perguntar por que seus cookies não são removidos mesmo após o envio deste cabeçalho. Nesse caso, consulte os cookies de outros domínios. Por exemplo, após a exclusão foo=bar; domain=www.example.com, um outro cookie foo=qux; domain=.example.comserá usado.
Lekensteyn
3
"O cliente pode configurar o navegador de forma que o cookie persista, mesmo se ele expirar. Definir o valor conforme descrito acima resolveria esse problema." O cliente não pôde configurar o navegador para ignorar sua solicitação para definir o conteúdo do cookie como "excluído" também? Você não tem como forçar o cliente a fazer o que não deseja.
Ajedi32
@ Ajedi32 Poderia, mas então você deve passar por um esforço adicional para fazê-lo (como cliente). O comportamento de ignorar um valor vazio é muito mais comum, não faria sentido para um navegador ignorar essas solicitações, especialmente para IDs de sessão que são invalidados.
Lekensteyn
2
-1 porque nunca vi uma maneira de configurar um navegador para ignorar a expiração de cookies e não estou convencido de que exista um navegador que ofereça essa opção. Além disso, a primeira frase da sua resposta, após a edição bastante ousada de @ DaveJarvis, agora é totalmente falsa para qualquer navegador principal ou qualquer agente de usuário compatível com especificações. tools.ietf.org/search/rfc6265#section-5.3 determina que "O agente do usuário DEVE remover todos os cookies expirados da loja de cookies se, a qualquer momento, existir um cookie expirado na loja de cookies". e, pelo que sei, é o que todo navegador de fato faz.
Mark Amery
46

No momento em que escrevi esta resposta, a resposta aceita para essa pergunta parece indicar que os navegadores não precisam excluir um cookie ao receber um cookie de substituição cujo Expiresvalor estava no passado. Essa afirmação é falsa. Definir Expirescomo no passado é a maneira padrão e compatível com as especificações de excluir um cookie, e os agentes do usuário são obrigados pelas especificações a respeitá-lo.

O uso de um Expiresatributo no passado para excluir um cookie está correto e é a maneira de remover cookies ditados pelas especificações. A seção de exemplos da RFC 6255 declara:

Por fim, para remover um cookie, o servidor retorna um cabeçalho Set-Cookie com uma data de validade no passado. O servidor terá êxito na remoção do cookie apenas se o atributo Path e Domain no cabeçalho Set-Cookie corresponderem aos valores usados ​​quando o cookie foi criado.

A seção Requisitos do agente do usuário inclui os seguintes requisitos, que juntos têm o efeito de que um cookie deve ser imediatamente eliminado se o agente do usuário receber um novo cookie com o mesmo nome cuja data de validade esteja no passado

  1. Se [ao receber um novo cookie] a loja de cookies contiver um cookie com o mesmo nome, domínio e caminho que o cookie recém-criado:

    1. ...
    2. ...
    3. Atualize o horário de criação do cookie recém-criado para corresponder ao horário de criação do cookie antigo.
    4. Remova o cookie antigo da loja de cookies.
  2. Insira o cookie recém-criado no armazenamento de cookies.

Um cookie está "expirado" se o cookie tiver uma data de validade no passado.

O agente do usuário DEVE remover todos os cookies expirados da loja de cookies se, a qualquer momento, existir um cookie expirado na loja de cookies.

Os pontos 11-3, 11-4 e 12 acima juntos significam que quando um novo cookie é recebido com o mesmo nome, domínio e caminho, o cookie antigo deve ser eliminado e substituído pelo novo cookie. Por fim, o ponto abaixo sobre cookies expirados determina ainda que, após isso, o novo cookie também deverá ser despejado imediatamente. A especificação não oferece espaço de manobra para os navegadores neste ponto; se um navegador oferecer ao usuário a opção de desativar a expiração de cookies, como a resposta aceita sugere que alguns navegadores o façam, isso viola as especificações. (Esse recurso também teria pouco uso e, tanto quanto eu sei, ele não existe em nenhum navegador.)

Por que, então, o OP desta pergunta observou essa abordagem falhando? Embora eu não tenha espanado uma cópia do Internet Explorer para verificar seu comportamento, suspeito que tenha sido porque o Expiresvalor do OP estava malformado! Eles usaram este valor:

expires=Thu, Jan 01 1970 00:00:00 UTC;

No entanto, isso é sintaticamente inválido de duas maneiras.

A seção de sintaxe da especificação determina que o valor do Expiresatributo deve ser um

rfc1123 -date, definido em [RFC2616], Seção 3.3.1

Seguindo o segundo link acima, encontramos isso como um exemplo do formato:

Sun, 06 Nov 1994 08:49:37 GMT

e descobrir que a definição de sintaxe ...

  1. exige que as datas sejam escritas no formato dia mês ano , não no formato mês dia ano , conforme usado pelo solicitante da pergunta.

    Especificamente, ele define da rfc1123-dateseguinte maneira:

    rfc1123-date = wkday "," SP date1 SP time SP "GMT"
    

    e define date1assim:

    date1        = 2DIGIT SP month SP 4DIGIT
                 ; day month year (e.g., 02 Jun 1982)
    

e

  1. não permite UTCcomo fuso horário.

    A especificação contém a seguinte declaração sobre quais compensações de fuso horário são aceitáveis ​​nesse formato:

    Todos os carimbos de data / hora HTTP DEVEM ser representados no horário médio de Greenwich (GMT), sem exceção.

    Além disso, se aprofundarmos nas especificações originais desse formato de data e hora, descobrimos que em suas especificações iniciais em https://tools.ietf.org/html/rfc822 , a seção Sintaxe lista "UT" (que significa "tempo universal" ) como um valor possível, mas não lista o UTC (Tempo Universal Coordenado) como válido. Até onde eu sei, o uso de "UTC" neste formato de data nunca foi válido; não era um valor válido quando o formato foi especificado pela primeira vez em 1982, e a especificação HTTP adotou uma versão estritamente mais restritiva do formato ao proibir o uso de todos os valores de "zona" que não sejam "GMT".

Se o autor da pergunta aqui tiver usado um Expiresatributo como este , então:

expires=Thu, 01 Jan 1970 00:00:00 GMT;

então, presumivelmente, teria funcionado.

Mark Amery
fonte
15

Definir "expira" para uma data anterior é a maneira padrão de excluir um cookie.

Seu problema é provavelmente porque o formato da data não é convencional. O IE provavelmente espera apenas GMT.

irreputável
fonte
2

Use Idade máxima = -1 em vez de "Expira". É mais curto, menos exigente quanto à sintaxe e a Max-Age tem precedência sobre o Expirar de qualquer maneira.

Steven Pemberton
fonte
-1

Para a implementação JAX-RS do GlassFish Jersey, resolvi esse problema pelo método comum, descrevendo todos os parâmetros comuns. Pelo menos três dos parâmetros devem ser iguais: nome (= "nome"), caminho (= "/") e domínio (= nulo):

public static NewCookie createDomainCookie(String value, int maxAgeInMinutes) {
    ZonedDateTime time = ZonedDateTime.now().plusMinutes(maxAgeInMinutes);
    Date expiry = time.toInstant().toEpochMilli();
    NewCookie newCookie = new NewCookie("name", value, "/", null, Cookie.DEFAULT_VERSION,null, maxAgeInMinutes*60, expiry, false, false);
    return newCookie;
}

E use-o da maneira comum de definir cookies:

NewCookie domainNewCookie = RsCookieHelper.createDomainCookie(token, 60);
Response res = Response.status(Response.Status.OK).cookie(domainNewCookie).build();

e para excluir o cookie:

NewCookie domainNewCookie = RsCookieHelper.createDomainCookie("", 0);
Response res = Response.status(Response.Status.OK).cookie(domainNewCookie).build();
RoutesMaps.com
fonte
para mim, quando defino maxAge como 0, ele gera um cookie com Max-Age = 0, que o Chrome parece ignorar. Na seção 4.1.1 da RFC 6265, especifica a sintaxe de Max-Age como "dígito diferente de zero". Essa pode ser a razão. Embora, como mencionado por @ JoshC13, a seção 5.2.2 fale sobre a interpretação de valores menores ou iguais a zero. Então, que tipo de si lá ... contradiz
Matthijs Wessels
Não sei detalhes, mas esses valores em pares estão realmente funcionando no Chrome e em outros navegadores: maxAgeInMinutes * 60, expiração.
RoutesMaps.com 31/08/19
1
@MatthijsWessels Good catch! Eu cavei um pouco mais fundo, e a aparente contradição é de fato intencional, como observado na errata em rfc-editor.org/errata/eid3430 . Para "maximizar a interoperabilidade", os agentes do usuário são obrigados a interpretar zero ou negativo Max-Agecomo a data e hora mais antiga representável, mas os servidores são proibidos de enviar esse Max-Agevalor. Eu acho que os autores conheciam os clientes existentes que não podiam manipular Max-Age=0e os servidores que os enviaram no momento em que escreveram as especificações, e tentaram mitigar o problema dos dois lados.
Mark Amery
@ Crimean.us Eu também não posso me reproduzir novamente. Talvez eu tenha feito algo errado
Matthijs Wessels
@MatthijsWessels Problema com ignorar Max-Age = 0 é corrigido no meu exemplo, definindo a data de validade como ZonedDateTime.now (). PlusMinutes (maxAgeInMinutes). Para maxAgeInMinutes = 0, é o horário e data atuais. Este código está funcionando por um longo tempo no aplicativo Web real.
RoutesMaps.com