227

For my authentication process I create a unique token when a user logs in and put that into a cookie which is used for authentication.

So I would send something like this from the server:

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

Which works on all browsers. Then to delete a cookie I send a similar cookie with the expires field set for January 1st 1970

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

And that works fine on Firefox but doesn't delete the cookie on IE or Safari.

So what is the best way to delete a cookie (without JavaScript preferably)? The set-the-expires-in-the-past method seems bulky. And also why does this work in FF but not in IE or Safari?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Joshkunz
  • 5,575
  • 6
  • 28
  • 24

6 Answers6

293

Sending the same cookie value with ; expires appended will not destroy the cookie.

Invalidate the cookie by setting an empty value and include an expires field as well:

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

Note that you cannot force all browsers to delete a cookie. The client can configure the browser in such a way that the cookie persists, even if it's expired. Setting the value as described above would solve this problem.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
  • 83
    I would recommend to use an empty text as rubbish, instead of `"deleted"`, to avoid confusion later with a potentially legal value equals to "deleted" – yegor256 Oct 29 '12 at 06:18
  • 9
    @raulk Yes, you are correct. Funny that it has not been noticed before, hopefully it did not cause too much issue. yegor256, an empty value should work in most cases. Related: some people may wonder why their cookies do not get removed even after sending this header. In that case, have a look at cookies from other domains. For example, after deleting `foo=bar; domain=www.example.com`, an other cookie `foo=qux; domain=.example.com` will be used. – Lekensteyn Jun 26 '13 at 13:23
  • 4
    "The client can configure the browser in such a way that the cookie persists, even if it's expired. Setting the value as described above would solve this problem." Couldn't the client could configure the browser to ignore your request to set the cookie contents to "deleted" too? You have no way to force the client to do anything it doesn't want to. – Ajedi32 Dec 23 '15 at 17:36
  • 1
    @Ajedi32 It could, but then you must go through additional effort to do so (as a client). The behavior of ignoring an empty value is much more common, it would not make sense for a browser to ignore such requests, especially for session IDs which are invalidated. – Lekensteyn Dec 24 '15 at 09:09
  • Courtesy https://stackoverflow.com/a/20320610/1895600 Do set the same path. – mayankcpdixit Jan 04 '18 at 06:46
  • @yegor256 The empty string is *also* a "potentially legal value" for a cookie, though, so there's no reason in principle why it should be any better. For the specific use-case of a session token (shown in the question here, and also perhaps the most common scenario in which you'd want to delete a cookie), presumably there's some fixed or minimum length that a token can have, and so both the empty string and "deleted" will clearly not be valid tokens. Given that, I'd typically prefer using "deleted", to convey more clearly what's going on to a dev looking at the response in their dev tools. – Mark Amery Nov 30 '18 at 15:01
  • 9
    -1 because I have never seen a way to configure a browser to ignore cookie expiration, and am unconvinced that any browser exists that offers such an option. What's more, the first sentence of your answer, after @DaveJarvis's rather bold edit, is now outright false for any major browser or any spec-compliant user agent. https://tools.ietf.org/search/rfc6265#section-5.3 dictates that *"The user agent MUST evict all expired cookies from the cookie store if, at any time, an expired cookie exists in the cookie store."* and to the best of my knowledge that is what every browser in fact does. – Mark Amery Dec 01 '18 at 17:24
  • @MarkAmery, I believe the edit held true for older versions of IE. If that's not the case, feel free to revert or revise the answer. – Dave Jarvis Dec 03 '18 at 07:44
  • @DaveJarvis I don't know for sure, since I haven't tested, whether there was a historical version of IE that ignored historical `expires` values (and I *can't* easily test it, now, since as far as I know Microsoft only distributes testing VMs for IE 8 onwards). However, I can't turn up any evidence that there was on Google... and even if it's *true*, that's not what your edit says. It just says that expires can't be used to destroy cookies, without any further qualification. – Mark Amery Dec 03 '18 at 22:42
  • @MarkAmery: That's a good point and the update isn't clear. I might have meant that `; expires` _alone_ won't destroy the cookie in all circumstances, implying that there must be a value given on the RHS of the expression (e.g., `; expires=0`). – Dave Jarvis Dec 04 '18 at 00:01
137

At the time of my writing this answer, the accepted answer to this question appears to state that browsers are not required to delete a cookie when receiving a replacement cookie whose Expires value is in the past. That claim is false. Setting Expires to be in the past is the standard, spec-compliant way of deleting a cookie, and user agents are required by spec to respect it.

Using an Expires attribute in the past to delete a cookie is correct and is the way to remove cookies dictated by the spec. The examples section of RFC 6265 states:

Finally, to remove a cookie, the server returns a Set-Cookie header with an expiration date in the past. The server will be successful in removing the cookie only if the Path and the Domain attribute in the Set-Cookie header match the values used when the cookie was created.

The User Agent Requirements section includes the following requirements, which together have the effect that a cookie must be immediately expunged if the user agent receives a new cookie with the same name whose expiry date is in the past

  1. If [when receiving a new cookie] the cookie store contains a cookie with the same name, domain, and path as the newly created cookie:

    1. ...
    2. ...
    3. Update the creation-time of the newly created cookie to match the creation-time of the old-cookie.
    4. Remove the old-cookie from the cookie store.
  2. Insert the newly created cookie into the cookie store.

A cookie is "expired" if the cookie has an expiry date in the past.

The user agent MUST evict all expired cookies from the cookie store if, at any time, an expired cookie exists in the cookie store.

Points 11-3, 11-4, and 12 above together mean that when a new cookie is received with the same name, domain, and path, the old cookie must be expunged and replaced with the new cookie. Finally, the point below about expired cookies further dictates that after that is done, the new cookie must also be immediately evicted. The spec offers no wiggle room to browsers on this point; if a browser were to offer the user the option to disable cookie expiration, as the accepted answer suggests some browsers do, then it would be in violation of the spec. (Such a feature would also have little use, and as far as I know it does not exist in any browser.)

Why, then, did the OP of this question observe this approach failing? Though I have not dusted off a copy of Internet Explorer to check its behaviour, I suspect it was because the OP's Expires value was malformed! They used this value:

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

However, this is syntactically invalid in two ways.

The syntax section of the spec dictates that the value of the Expires attribute must be a

rfc1123-date, defined in [RFC2616], Section 3.3.1

Following the second link above, we find this given as an example of the format:

Sun, 06 Nov 1994 08:49:37 GMT

and find that the syntax definition...

  1. requires that dates be written in day month year format, not month day year format as used by the question asker.

    Specifically, it defines rfc1123-date as follows:

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

    and defines date1 like this:

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

and

  1. doesn't permit UTC as a timezone.

    The spec contains the following statement about what timezone offsets are acceptable in this format:

    All HTTP date/time stamps MUST be represented in Greenwich Mean Time (GMT), without exception.

    What's more if we dig deeper into the original spec of this datetime format, we find that in its initial spec in https://www.rfc-editor.org/rfc/rfc822, the Syntax section lists "UT" (meaning "universal time") as a possible value, but does not list not UTC (Coordinated Universal Time) as valid. As far as I know, using "UTC" in this date format has never been valid; it wasn't a valid value when the format was first specified in 1982, and the HTTP spec has adopted a strictly more restrictive version of the format by banning the use of all "zone" values other than "GMT".

If the question asker here had instead used an Expires attribute like this, then:

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

then it would presumably have worked.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • 4
    A cleaner way to do this would be to generate such string. For example in JS: `new Date(0).toUTCString()` – Herobrine Oct 15 '20 at 10:43
  • 2
    a user agent may choose not to follow the "required" rules. The accepted answer is simply stating that in theory, a browser may continue to send the expired cookie. – CaptainCodeman Dec 22 '20 at 12:38
  • My error was to not include the same path "/" as I did at creation. Thanks – Jahrenski Jun 15 '21 at 19:06
  • 1
    Looks like safari 16 ignores `expires` completely (future and past dates) and falls back to `Session` instead, making it impossible to delete cookies like this. – fabb Nov 25 '22 at 11:12
  • A session cookie, where Chrome dev tools show Expires / Max-Age = "Session" in the Application tab, will also get deleted when the cookie is set again with Max-Age=0. However for me, setting the cookie worked on localhost with different ports for the backend api and web app but not deleting it. When deployed with backend api and web app on the same domain and port, the session cookie got deleted. – David Apr 04 '23 at 13:08
38

Use Max-Age=-1 rather than "Expires". It is shorter, less picky about the syntax, and Max-Age takes precedence over Expires anyway.

  • 4
    use `Max-Age=0` rather than `-1` value. – tdjprog Oct 31 '21 at 17:36
  • 4
    @tdjprog Please explain why, as both do work. "A zero or negative number will expire the cookie immediately", source : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie – Musa Dec 26 '21 at 19:17
  • 2
    you are right @Musa. zero or negative number – tdjprog Dec 26 '21 at 22:31
  • In chrome 105.0.5195.127 - I just see that when setting Max-Age to -1 I get Expires: Sunday, September 20, 5838270 at 2:20:54 AM. Where 5838270 indicates the Year - this is freakin odd. (this is when i check the cookie params via the url address bar small lock icon, on the left of the address) – Mercury Oct 01 '22 at 18:12
18

Setting "expires" to a past date is the standard way to delete a cookie.

Your problem is probably because the date format is not conventional. IE probably expects GMT only.

irreputable
  • 44,725
  • 9
  • 65
  • 93
0

This is the obvious answer that works - when you dont know the cookie name but you need to sterilize the request

req.cookies = ''

You Are Welcome

-1

For GlassFish Jersey JAX-RS implementation I have resolved this issue by common method is describing all common parameters. At least three of parameters have to be equal: name(="name"), path(="/") and domain(=null) :

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;
}

And use it the common way to set cookie:

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

and to delete the cookie:

NewCookie domainNewCookie = RsCookieHelper.createDomainCookie("", 0);
Response res = Response.status(Response.Status.OK).cookie(domainNewCookie).build();
RoutesMaps.com
  • 1,628
  • 1
  • 14
  • 19
  • for me when I set maxAge to 0, it outputs a cookie with Max-Age=0 which Chrome seems to ignore. In [RFC 6265 section 4.1.1](https://tools.ietf.org/html/rfc6265#section-4.1.1) it specifies the syntax of Max-Age as "non-zero-digit". That might be the reason. Although, as mentioned by @JoshC13, section 5.2.2 does talk about interpreting values less than or equal to zero. So it kind of contradicts itself there... – Matthijs Wessels Aug 30 '18 at 07:19
  • I don't know details, but these values in pair are really working in Chrome and other browsers: maxAgeInMinutes*60, expiry. – RoutesMaps.com Aug 31 '18 at 09:11
  • 1
    @MatthijsWessels Good catch! I dug a little deeper, and the apparent contradiction is in fact intentional, as noted in the errata at https://www.rfc-editor.org/errata/eid3430. To "maximize interoperability", *user agents* are required to interpret a zero or negative `Max-Age` as the earliest representable date and time, but *servers* are forbidden from sending a such a `Max-Age` value. I guess the authors knew of both existing clients that couldn't handle `Max-Age=0` and servers that sent it at the time that they wrote the spec, and tried to mitigate the problem from both ends. – Mark Amery Dec 01 '18 at 16:37
  • @Crimean.us I can't repro anymore either. Maybe I did something wrong – Matthijs Wessels Dec 01 '18 at 20:29
  • @MatthijsWessels Problem with ignore Max-Age=0 is fixed in my example by setting expiry date to ZonedDateTime.now().plusMinutes(maxAgeInMinutes). For maxAgeInMinutes=0 it is current datetime. This code is working for a long time in the real web application. – RoutesMaps.com Dec 06 '18 at 14:54