Cookies on localhost with explicit domain
Asked Answered
W

26

293

I must be missing some basic thing about cookies. On localhost, when I set a cookie on server side and specify the domain explicitly as localhost (or .localhost). the cookie does not seem to be accepted by some browsers.

Firefox 3.5: I checked the HTTP request in Firebug. What I see is:

Set-Cookie:
    name=value;
    domain=localhost;
    expires=Thu, 16-Jul-2009 21:25:05 GMT;
    path=/

or (when I set the domain to .localhost):

Set-Cookie:
    name=value;
    domain=.localhost;
    expires=Thu, 16-Jul-2009 21:25:05 GMT;
    path=/

In either case, the cookie is not stored.

IE8: I did not use any extra tool, but the cookie does not seem to be stored as well, because it’s not being sent back in subsequent requests.

Opera 9.64: Both localhost and .localhost work, but when I check the list of cookies in Preferences, the domain is set to localhost.local even though it’s listed under localhost (in the list grouping).

Safari 4: Both localhost and .localhost work, but they are always listed as .localhost in Preferences. On the other hand, a cookie without an explicit domain, it being shown as just localhost (no dot).

What is the problem with localhost? Because of such a number of inconsistencies, there must be some special rules involving localhost. Also, it’s not completely clear to me why domains must be prefixed by a dot? RFC 2109 explicitly states that:

The value for the Domain attribute contains no embedded dots or does not start with a dot.

Why? The document indicates that it has to do something with security. I have to admit that I have not read the entire specification (may do it later), but it sounds a bit strange. Based on this, setting cookies on localhost would be impossible.

Willin answered 15/7, 2009 at 21:47 Comment(9)
6 years old thread and this still is a problem. I'm using Chrome v40. See here.Multiplechoice
11 years later, still doesn't work, cookies are still a pain in the butt in 2020!Mongrelize
This might not answer the question. In chrome version 80 you can disable 'Cookies without SameSite must be secure' in chrome://flags to allow to use SameSite=None or SameSite=Lax instead of only Secure.Shannanshannen
I think we can do that in Firefox 83.0 from now on but still not in chrome 87Hymenopteran
13 years later and this is still a pain in the ass in 2022, funnily enough not one of the companies developing web browsers have thought that requests from localhost shouldn't suffer from cookies restrictions.Spiller
13 years later we are still here.Peloria
nov 21 2022 , the issue is still there.Freed
In Firefox ensure Enhanced Tracking Protection is disabled on localhost. If all other cookie related settings are correct, ETP (in strict mode) will still stop the cookie being attached.Pines
I'm from the future and I can assure you, that this is still not easy.Bowfin
D
328

By design, domain names must have at least two dots; otherwise the browser will consider them invalid. (See reference on http://curl.haxx.se/rfc/cookie_spec.html)

When working on localhost, the cookie domain must be omitted entirely. You should not set it to "" or NULL or FALSE instead of "localhost". It is not enough.

For PHP, see comments on http://php.net/manual/en/function.setcookie.php#73107.

If working with the Java Servlet API, don't call the cookie.setDomain("...") method at all.

Deas answered 27/7, 2009 at 13:24 Comment(15)
I don't see anywhere in RFC6265 about the two dots in the domain: tools.ietf.org/html/rfc6265#section-5.2.3 .Net says set it to ".local" for all hosts in your local domain. Which seems consistent with Opera/Safari msdn.microsoft.com/en-us/library/ckch3yd2.aspxErnestoernestus
In languages like PHP null and false are equivalent to an empty string. Setting the cookie domain to an empty string works. I use this setting in my projects.Rimola
@Justin: Hm, you probably need to completely omit the Domain= parameter when setting the cookie. If you just set the domain to null or empty, maybe your framework will send the Domain= parameter with that value, instead of omitting it? Check with e.g. Firebug.Tragedian
@Ernestoernestus two dots in a domain is standard, localhost is a top level domain. This is why localhost.lvh.me existsDulciana
This is somewhat poorly worded. "Setting to null or false or empty string" should read "Not setting the 'domain' portion of the cookie at all." For example, using a simple test to completely leave out the domain section of the cookie works for localhost: ((domain && domain !== "localhost") ? ";domain="+domain : "")Harlie
Chrome appears to save "Session" cookies for localhost (cookies that set no expiration, such as "ASP.NET_SessionId"), but not "Permanent" cookies (cookies that do set an expiration). The name "permanent" is a bit counter-intuitive. Session cookies (no expiration) are supposed to be deleted when the browser closes, but modern browsers have a "continue where I left off" feature that hangs onto them across browser restarts. Confusing. Anyway, Chrome is absolutely, positively IGNORING permanent cookies sent in the response for "localhost" domain; it refuses the save them or send the back.Fowlkes
I visit instead localhost:8080/mysite this URL 127.0.0.1:8080/mysite. Cause there are at least two dots, this is a valid domain (127.0.0.1) and the browser (in my case chrome) sets the cookie successfullyRinglet
This did not work for me either. I was able to fix it by making a request from the same host name as the receiving client. localhost:someport -> localhost:otherport. I then had to include credentials: "include" in the request options. Then the cookies worked. Also note that it does not work for chrome. try Firefox first.Boss
I think we can do that in Firefox 83.0 from now on but still not in chrome 87Hymenopteran
@Justin: Read more carefully: Must be omitted. I change an answer a bit. Now it more clean: You should not set it to "" or NULL or false. It is not enoughTiffanytiffi
won't 127.0.0.1 work if it needs two dots?Nonprofit
That does not work not on firefox nor on chrome as of this day.Spiller
Omit the domain on cookie works for my on local host chorme: user=KVKYR%2FdPfyoV2oHo5jU3hvjfZZOykNReh1YoAzJLR0w%3D; Path=/; HttpOnly; Secure; SameSite=NoneJocko
Note: That PHP note seems to be wrong now. I can only get setcookie to work if I use localhost as the domain. Everything else does not seem to work with php/firefox.Supplicatory
This doesn't seem to be correct. I just tested with domain set to empty string and it works correctly on localhost.Income
P
51

I broadly agree with @Ralph Buchfelder, but here's some amplification of this, by experiment when trying to replicate a system with several subdomains (such as example.com, fr.example.com, de.example.com) on my local machine (OS X / Apache / Chrome|Firefox).

I've edited /etc/hosts to point some imaginary subdomains at 127.0.0.1:

127.0.0.1 localexample.com
127.0.0.1 fr.localexample.com
127.0.0.1 de.localexample.com

If I am working on fr.localexample.com and I leave the domain parameter out, the cookie is stored correctly for fr.localexample.com, but is not visible in the other subdomains.

If I use a domain of ".localexample.com", the cookie is stored correctly for fr.localexample.com, and is visible in other subdomains.

If I use a domain of "localexample.com", or when I was trying a domain of just "localexample" or "localhost", the cookie was not getting stored.

If I use a domain of "fr.localexample.com" or ".fr.localexample.com", the cookie is stored correctly for fr.localexample.com and is (correctly) invisible in other subdomains.

So the requirement that you need at least two dots in the domain appears to be correct, even though I can't see why it should be.

If anyone wants to try this out, here's some useful code:

<html>
<head>
<title>
Testing cookies
</title>
</head>
<body>
<?php
header('HTTP/1.0 200');
$domain = 'fr.localexample.com';    // Change this to the domain you want to test.
if (!empty($_GET['v'])) {
    $val = $_GET['v'];
    print "Setting cookie to $val<br/>";
    setcookie("mycookie", $val, time() + 48 * 3600, '/', $domain);
}
print "<pre>";
print "Cookie:<br/>";
var_dump($_COOKIE);
print "Server:<br/>";
var_dump($_SERVER);
print "</pre>";
?>
</body>
</html>
Powell answered 20/5, 2013 at 14:58 Comment(0)
C
48

localhost: You can use: domain: ".app.localhost" and it will work. The 'domain' parameter needs 1 or more dots in the domain name for setting cookies. Then you can have sessions working across localhost subdomains such as: api.app.localhost:3000.

Coryphaeus answered 27/2, 2014 at 13:45 Comment(5)
Also tested and working on a node.js server, using Express 3.x, in express.session({cookie: { domain: '.app.localhost', maxAge: 24 * 60 * 60 * 1000 }})Coryphaeus
THIS should be selected as an answer if you are using local domains! Putting a dot before the subdomain fixes my issue.Type
So, where does this prepending of the .app. coming from? Is it part of some SPEC? And is it applicable for all non-conforming domains (those without two dots)? Also, will this work with old browsers? :^)Unreflecting
Oh... I understand now... It is just a trick to fool the browsers. OK.Unreflecting
I'm having the same issue here but with https protocol. Please check it out here: #76149072Lashondalashonde
G
34

Cross sites cookies problem I solved like this:

Backend

Server side

  • serving on: http://localhost:8080
  • when creating a response, set Cookie

attributes:

SameSite=None; Secure; Path=/

Client side

Frontend (in my case Angular)

  • serving on: http://localhost:4200/
  • when sending request to Server (backend)

set XHR.withCredentials=true:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8080/', true);
xhr.withCredentials = true;
xhr.send(null);

My interpretation:

  • when backend and frontend domains differ the decision if the cookies will be saved in frontend domain cookie storage from received response is brought by the browser. Browser will allow sending cookies ONLY if XHR request has withCredentials=true and correct server Cookie attributes (HTTP Set-Cookie header) are recieved

  • when backend and frontend domains differ the decision if the cookies will be sent within request is brought by the browser. Browser will allow this ONLY if XHR request has withCredentials=true

  • in other words, if withCredentials=true is ommited - cookies won't be sent within request NOR will be recieved and saved from response

  • recieved cookies are allways stored under frontend domain name in browser cookie storage. In case when server domain differs and cookies are saved successfully, the effect is the same as if they have been sent by frontend domain in the first place.

  • if SameSite=None cookie attribute is omitted today's browser (Firefox/Chrome) will use default Lax mode which is too strict for cross site cookies

  • if Secured cookie attribute is ommited - then SameSite=None will be ignored - it requires Secured to be set

  • for localhost Secured cookie property browser does not require HTTPS / SSL, http will work - no need to serve frontend or backend under https://localhost ...

EDIT 2022-03-02 - For Safari (v15.1) this is not true -> in Safari http://localhost + cookie with Secure - the cookie will be ignored, not saved in browser (solution: for Safari + http://localhost remove Secure and SameSite if provided).

EDIT 2023-01-13 - @Barnaby reported that "Firefox refuses to set it: 'has been rejected because a non-HTTPS cookie can’t be set as “secure”.'" If this is the case - solution as for Safari should work (see EDIT 2022-03-02 above).

Hints for diagnostics:

  • in order to check if the cookies are sent - open browser developer tools and check Network tab. Find the request to backend and check Headers - search for Cookie header in Request headers, and Set-Cookie in Response headers
  • in order to check if the cookies are saved - open browsers developer tools, see Storage manager (Firefox), check Cookies and search for frontend domain name, check if the cookie exists and if does, check when it was created ...
  • don't forget to set CORS on backend first

Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie

Gondolier answered 9/12, 2021 at 15:51 Comment(3)
This answer should be higher up. I'm using java and I had to set the attributes exactly like Robert said. You can't even have something like Path=/api it has to be Path=/ so my whole cookie setup on backend is this: ResponseCookie cookie = ResponseCookie.from(jwtCookie, jwt) .path("/") .maxAge(24 * 60 * 60) .sameSite("None") .secure(true) .httpOnly(true) .build();Bronchitis
I'm having the same issue here but with https protocol. Please check it out here: #76149072Lashondalashonde
This line did it for me: in other words, if withCredentials=true is ommited - cookies won't be sent within request NOR will be recieved and saved from response. On the call to the azure function that gets the cookie response, I didn't have this set, only on subsequent requests did I have this set, thinking I only needed it for requests where the cookie needed to be sent and not received. Thank you.Claim
R
24

When a cookie is set with an explicit domain of 'localhost' as follows...

Set-Cookie: name=value; domain=localhost; expires=Thu, 16-Jul-2009 21:25:05 GMT; path=/

...then browsers ignore it because it does not include at least two periods and is not one of seven specially handled, top level domains.

...domains must have at least two (2) or three (3) periods in them to prevent domains of the form: ".com", ".edu", and "va.us". Any domain that fails within one of the seven special top level domains listed below only require two periods. Any other domain requires at least three. The seven special top level domains are: "COM", "EDU", "NET", "ORG", "GOV", "MIL", and "INT".

Note that the number of periods above probably assumes that a leading period is required. This period is however ignored in modern browsers and it should probably read...

at least one (1) or two (2) periods

Note that the default value for the domain attribute is the host name of the server which generated the cookie response.

So a workaround for cookies not being set for localhost is to simply not specify a domain attribute and let the browser use the default value - this does not appear to have the same constraints that an explicit value in the domain attribute does.

Ria answered 25/8, 2015 at 17:21 Comment(3)
I didn't DV, but I'm guessing the reason that others did is because your answer doesn't really add much value. The two periods requirement and leaving the domain attribute blank have both been discussed in other answers. Also, the stuff you added about a top level domain seems to be incorrect. In my experience that is not a requirement.Ventral
@Ventral Not sure if you got to the bit in my answer where I say that it should be at least 1 or two periods depending on the TLD because leading periods are ignored? So I provided some background on the problem and added a point that I don't think is covered elsewhere - the rules are different for an explicit domain and the one that the browser defaults to. Seems like it adds some value to me.Ria
Leaving the domain null (not setting it at all) does NOT cause Chrome to keep the cookie for localhost. It still ignores it. Note that this only applies to "permanent" cookies (ones that set an expiration date), because it will hang on to "session" cookies for localhost (ones that don't set an expiration date).Fowlkes
C
13

If you're setting a cookie from another domain (ie you set the cookie by making an XHR cross origin request), then you need to make sure you set the withCredentials attribute to true on the XMLHttpRequest you use to fetch the cookie as described here

Centralism answered 23/1, 2019 at 11:40 Comment(2)
yes even with that. It still doesn't work with cross-domain requests. Browser - Safari, IE 11Kisangani
This is the only one that helped me (Chrome, client - vite js, server - node + express).Barbet
A
6

Using PHP; nothing on this page worked for me. I eventually realized in my code that the secure parameter to PHP's session_set_cookie_params() was always being set to TRUE.

Since I wasn't visiting localhost with HTTPS, my browser would never accept the cookie. So, I modified that portion of my code to conditionally set the secure param based on $_SERVER['HTTP_HOST'] being localhost or not. It works well now.

Azriel answered 15/12, 2016 at 9:16 Comment(0)
B
4

I had much better luck testing locally using 127.0.0.1 as the domain. I'm not sure why, but I had mixed results with localhost and .localhost, etc.

Betsey answered 1/4, 2012 at 18:28 Comment(0)
W
4

Results I had varied by browser.

Chrome- 127.0.0.1 worked but localhost .localhost and "" did not. Firefox- .localhost worked but localhost, 127.0.0.1, and "" did not.

Have not tested in Opera, IE, or Safari

Waylan answered 23/8, 2012 at 13:17 Comment(2)
Just tested it with Chrome V.22.0.1229.94 m: Setting a cookie for localhost without giving a Domain= parameter works. Domain= also works, but Domain=localhost does not.Tragedian
Domain=localhost worked for both Chrome and Firefox here, just remember to set the flag withCredentials: true in axios, or the equivalent of your http client js.Cloaca
P
4

After much experimentation and reading various posts, this worked. I could set multiple cookies, read them back and set the time negative and delete them.

func addCookie(w http.ResponseWriter, name string, value string) {
    expire := time.Now().AddDate(0, 0, 1)
    cookie := http.Cookie{
       Name:    name,
       Value:   value,
       Expires: expire,
       Domain:  ".localhost",
       Path:    "/",
    }
    http.SetCookie(w, &cookie)
}
Psf answered 7/11, 2019 at 19:44 Comment(3)
Does not work for me. Using "..", "localhost", ".localhost", nothing seems to work.Compress
I ended up using the Alex Edwards Session Manager (in my case with MySQL, but he gives you other options) and that works great. You might want to consider the same alexedwards.net/blog/scs-session-managerPsf
I modified /etc/hosts to work with localhost.com, which works fine too.Compress
D
4

The only thing that worked for me was to set Path=/ on the cookie.

Moreover, the default value of a path attribute seems to be different from browsers to browsers although I tested only two of them (Firefox and Chrome).

Chrome tries to set a cookie as is; if path attribute is omitted in Set-Cookie header then it will not be stored and ignored.

However, Firefox stores a cookie even without an explicit path attribute. It just set it with the requested path; my request url was /api/v1/users and the path was set to /api/v1 automatically.

Anyway, both browsers worked when path was set to / even without an explicit domain, ie Domain=localhost or something. So there are some differences in the way how each browser handles cookies.

Demote answered 24/11, 2019 at 2:36 Comment(0)
R
3

you can make use of localhost.org or rather .localhost.org it will always resolve to 127.0.0.1

Rafaelrafaela answered 27/3, 2019 at 19:40 Comment(3)
I would recommend against doing this security-wise, as localhost.org is an actual domain pointing to 127.0.0.1. We don't know much about the owner, and they could hijack the traffic only by changing the pointing address to a malicious IP.Cookgeneral
or you could set up your own domain to point to 127.0.0.1Terrance
or do that in /etc/hosts => 127.0.0.1 app-local.net cookie domain: .app-local.netTitanomachy
D
2

None of the suggested fixes worked for me - setting it to null, false, adding two dots, etc - didn't work.

In the end, I just removed the domain from the cookie if it is localhost and that now works for me in Chrome 38.

Previous code (did not work):

document.cookie = encodeURI(key) + '=' + encodeURI(value) + ';domain=.' + document.domain + ';path=/;';

New code (now working):

 if(document.domain === 'localhost') {
        document.cookie = encodeURI(key) + '=' + encodeURI(value) + ';path=/;' ;
    } else {
        document.cookie = encodeURI(key) + '=' + encodeURI(value) + ';domain=.' + document.domain + ';path=/;';
    }
Delanty answered 5/11, 2014 at 3:42 Comment(0)
H
2

There seems to be an issue when you use https://<local-domain> and then http://<local-domain>. The http:// site does not send cookies with requests after https:// site sets them. Force reload and clear cache doesn't help. Only manual clearing of cookies works. Also, if I clear them on the https:// page, then http:// page starts working again.

Looks to be related to "Strict secure cookies". Good explanation here. It was released in Chrome 58 on 2017-04-19.

It looks like Chrome does in fact record both secure cookies and non-secure cookies as it will show the correct cookies depending on the page's protocol when clicking the address bar icon.

But Developer tools > Application > Cookies will not show a non-secure cookie when there is a secure cookie of the same name for the same domain, nor will it send the non-secure cookie with any requests. This seems like a Chrome bug, or if this behavior is expected, there should be some way to view the secure cookies when on a http page and an indication that they are being overridden.

Workaround is to use different named cookies depending on if they are for an http site or https site, and to name them specific to your app. A __Secure- prefix indicates that the cookie should be strictly secure, and is also a good practice because secure and non-secure won't collide. There are other benefits to prefixes too.

Using different /etc/hosts domains for https vs. http access would work too, but one accidental https://localhost visit will prevent any cookies of the same names to work on http://localhost sites - so this is not a good workaround.

I have filed a Chrome bug report.

Hop answered 14/5, 2018 at 14:19 Comment(0)
C
2

Tried all of the options above. What worked for me was:

  1. Make sure the request to server have withCredentials set to true. XMLHttpRequest from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request.
  2. Do not set Domain
  3. Set Path=/

Resulting Set-Cookie header:

Set-Cookie: session_token=74528588-7c48-4546-a3ae-4326e22449e5; Expires=Sun, 16 Aug 2020 04:40:42 GMT; Path=/
Caramel answered 16/8, 2020 at 21:41 Comment(0)
N
2

Cookie needs to specify SameSite attribute, None value used to be the default, but recent browser versions made Lax the default value to have reasonably robust defense against some classes of cross-site request forgery (CSRF) attacks.

Along with SameSite=Lax you should also have Domain=localhost, so your cookie will be associated to localhost and kept. It should look something like this:

document.cookie = `${name}=${value}${expires}; Path=/; Domain=localhost; SameSite=Lax`;

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite

Neau answered 19/9, 2020 at 4:59 Comment(0)
L
1

There is an issue on Chromium open since 2011, that if you are explicitly setting the domain as 'localhost', you should set it as false or undefined.

Lanza answered 14/7, 2015 at 20:58 Comment(1)
This was the only solution that worked for me, setting Domain: undefined and Path: '/'Anurous
G
1

I had the same issue and I fixed it by putting 2 dots in the cookie name itself without specifying any domain.

set-cookie: name.s1.s2=value; path=/; expires=Sun, 12 Aug 2018 14:28:43 GMT; HttpOnly
Goodfornothing answered 24/1, 2018 at 15:22 Comment(1)
Unfortunately this did not work!Agonized
I
1

I had a similar problem where my backend and frontend were running on localhost but different ports. To fix this I omitted the Domain in the Set-Cookie and used withCredentials: true in my request options.

see here

Immedicable answered 11/10, 2021 at 7:31 Comment(2)
Unforunately this did not work as well :(Agonized
@Agonized Is your issue the same as the question above? It would be helpful if you shared more information about your issue. Other information like the language/framework used may be beneficial as well.Immedicable
B
0

Another important detail, the expires= should use the following date time format: Wdy, DD-Mon-YYYY HH:MM:SS GMT (RFC6265 - Section 4.1.1).

Set-Cookie:
  name=value;
  domain=localhost;
  expires=Thu, 16-07-2019 21:25:05 GMT;
  path=/
Bugs answered 15/6, 2011 at 5:15 Comment(0)
M
0
document.cookie = valuename + "=" + value + "; " + expires + ";domain=;path=/";

This "domain=;path=/"; will take a dynamic domain and its cookie will work in subdomains.

If you want to test it in localhost, it will work.

Magbie answered 7/10, 2014 at 9:35 Comment(0)
T
0

None of the answers here worked for me. I fixed it by putting my PHP as the very very first thing in the page.

Like other headers, cookies must be sent before any output from your script (this is a protocol restriction). This requires that you place calls to this function prior to any output, including and tags as well as any whitespace.

From http://php.net/manual/en/function.setcookie.php

Trichotomy answered 9/1, 2015 at 23:29 Comment(1)
that has nothing to do with the issue though, that's just not making the mistake of sending any other output before headersBystreet
F
0

I was playing around a bit.

Set-Cookie: _xsrf=2|f1313120|17df429d33515874d3e571d1c5ee2677|1485812120; Domain=localhost; Path=/

works in Firefox and Chrome as of today. However, I did not find a way to make it work with curl. I tried Host-Header and --resolve, no luck, any help appreciated.

However, it works in curl, if I set it to

Set-Cookie: _xsrf=2|f1313120|17df429d33515874d3e571d1c5ee2677|1485812120; Domain=127.0.0.1; Path=/

instead. (Which does not work with Firefox.)

Fauman answered 30/1, 2017 at 21:41 Comment(0)
T
0

Thanks to saied's answer, setting the cookie option to domain: ".localhost" and secure: true worked in Edge on macOS. However, in order for Safari to work I had to set secure: false which prevents Edge from working.

Tumefy answered 10/8, 2023 at 19:42 Comment(0)
M
0

Python's SimpleCookie snippet, which got it working for me:

cookie = cookies.SimpleCookie()
cookie['sessionid'] = "mock_sessionid"

c = cookie['sessionid']
c['httponly'] = False
c['secure'] = True
c['samesite'] = 'none'
c['path'] = '/'

cookie_value = cookie.output(header='')
headers = {"Set-Cookie": cookie_value}

I access the website on 127.0.0.1 in the browser.

Mady answered 14/2 at 14:7 Comment(0)
P
-3

If anyone is still facing this, I found that switching from a post request to a get request was needed.

I was using axios, and withCredentials: true on the frontend, but this was failing. Switching the request to get and changing my backend to match worked.

Pantagruel answered 17/10, 2022 at 22:33 Comment(1)
This has nothing to do with cookies. You are just accessing an endpoint with the wrong method.Dunbarton

© 2022 - 2024 — McMap. All rights reserved.