29

I have problem with setting a cookies via express. I'm using Este.js dev stack and I try to set a cookie in API auth /login route. Here is the code that I use in /api/v1/auth/login route

res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999)});
res.status(200).send({user, token: jwt.token});

In src/server/main.js I have registered cookie-parser as first middleware

app.use(cookieParser());

The response header for /api/v1/auth/login route contains

Set-Cookie:token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ.. 

but the cookie isn't saved in browser (document.cookie is empty, also Resources - Cookies tab in develepoers tools is empty) :(

EDIT: I'm found that when I call this in /api/v1/auth/login (without call res.send or res.json)

res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999), httpOnly: false}); next();

then the cookie is set AND response header has set X-Powered-By:Este.js ... this sets esteMiddleware in expres frontend rendering part.

When I use res.send

res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999), httpOnly: false}).send({user, token: jwt.token});`
next();

then I get error Can't set headers after they are sent. because send method is used, so frontend render throw this error.

But I have to send a data from API, so how I can deal with this?

Can some help me please? Thanks!

Mira
  • 301
  • 1
  • 3
  • 6
  • Do you realize that it's `document.cookie`, not `document.cookies`? And, when you look for the cookies are you in a page with the exact same domain as `/api/v1/auth/login` was sent to? – jfriend00 Apr 24 '16 at 16:16
  • 1
    Sorry for typo error, sure `document.cookie` is empty (edited). Yes, it's same domain everything is at `http://localhost:8000/` – Mira Apr 24 '16 at 16:23
  • @Mira Is the cookie available server-side in later requests – [`req.cookies.token`](http://expressjs.com/en/4x/api.html#req.cookies)? What other options are given in the `Set-Cookie` header after the value? – Jonathan Lonowski Apr 24 '16 at 16:41
  • @JonathanLonowski `req.cookies` is empty object `{}` ... here is complete `Set-Cookie` `Set-Cookie:token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJfaWQiOiJ2VXNlci8xNDA5Mjc0OTYwODc4NCIsImlhdCI6MTQ2MTUxNDc4MCwiZXhwIjoxNDYyOTc2Mjk1Njc2fQ.G46f18mjZgvIQwCw-uUr6wuF8mkoH-SgzNW5UyyTCUbI6PDiDhkZbBMkvIzUofRfRqNnxKmKWzyhQ79zjClocdzB6JH2niDLFMAMSxE36zqUOcc5C0z6FY5gu9z3dyT0zqnvTxR1DX1mijl-r-K_UOOc5Pf2D-8dwiN-V3ELTIObWnuP65KLDgR6kqRvCXU5_DGamroIlwiAfGEiPU-NeIDWK0yJTB1NNpBLBh9SuEtq38oSZ9n6pRCcrBGfCYuErkDvgT5p_-GWk8_IWr0U3UsXtsE89F5lVdkSRJpdQDH-psDP7n8jjCDd-hrBusIoRtl_JjEtU5wV4cmcaEakPQ; Path=/; HttpOnly` – Mira Apr 24 '16 at 18:16
  • 1
    @JonathanLonowski As I said all requests are from same domain. I also previously tried to change `httpOnly: false` in `res.cookies` options but without any effect :( – Mira Apr 24 '16 at 18:31
  • 2
    Main problem is that the cookie isn't saved in browser at all . – Mira Apr 24 '16 at 18:33
  • @Mira Could there be any other code, perhaps a module your application is using, which could be setting the cookie? As well as including `HttpOnly` when you didn't set it, the header doesn't mention the `Expires` option to match your snippet. – Jonathan Lonowski Apr 24 '16 at 18:46
  • @JonathanLonowski No, there is no other server part where I set a cookie. Sorry I send you wrong Set-cookie (for other code snippet, I still trying to solve it) here is Set-cookie for this code `res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999), httpOnly: false}).send({user, token: jwt.token}).end();` `Set-Cookie:token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ.....; Path=/; Expires=Sun, 24 Apr 2016 21:36:05 GMT` – Mira Apr 24 '16 at 18:53
  • Do you see the `Set-Cookie` header in the response from the server? – Yuri Zarubin Apr 24 '16 at 19:10
  • @Mira Had same issue, but actually https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials is what you need to have cookies back to server. – asa Feb 08 '17 at 13:07

10 Answers10

60

I had the same issue. The server response comes with cookie set:

Set-Cookie:my_cookie=HelloWorld; Path=/; Expires=Wed, 15 Mar 2017 15:59:59 GMT 

But the cookie was not saved by a browser.

This is how I solved it.

I use fetch in a client-side code. If you do not specify credentials: 'include' in fetch options, cookies are neither sent to server nor saved by a browser, although the server response sets cookies.

Example:

var headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');

return fetch('/your/server_endpoint', {
    method: 'POST',
    mode: 'same-origin',
    redirect: 'follow',
    credentials: 'include', // Don't forget to specify this if you need cookies
    headers: headers,
    body: JSON.stringify({
        first_name: 'John',
        last_name: 'Doe'
    })
})

Hope it helps somebody.

Green
  • 21,978
  • 45
  • 138
  • 223
  • 9
    You sir, are life saver! – Mali Naeemi Dec 18 '17 at 12:35
  • 3
    Wow. The `credentials` part really was the problem. I was banging my head all day. Thanks! Can you please explain why that is needed? – Ivan Apr 05 '18 at 09:39
  • 1
    `credentials: 'include'` was the answer. I also had to properly configure CORS on my backend, and it worked nicely. Thanks!!! – Alisson Apr 10 '19 at 05:28
  • 3
    Just a note that this isn't as much as "express" not setting the cookie but rather browser not allowing cookie unless you explicitly tell it to with "credentials" field. Do note that `credentials: 'include'` will set cookie from any domain while `credentials:'same-site'` will only set cookie from the same domain as the client. [Source](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included) – shriek Aug 22 '19 at 21:12
  • Its actually `credentials: "same-origin"` according to MDN docs for fetch API. Read more : https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch – Smit Patel Jan 02 '21 at 23:23
8

Struggling with this for a 3h, and finally realized, with axios, I should set withCredentials to true, even though I am only receiving cookies.

axios.defaults.withCredentials = true;

DedaDev
  • 1,838
  • 1
  • 12
  • 18
6

i work with express 4 and node 7.4 and angular,I had the same problem me help this:
a) server side: in file app.js i give headers to all response like:

 app.use(function(req, res, next) {  
      res.header('Access-Control-Allow-Origin', req.headers.origin);
      res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
      next();
 });  

this must have before all router.
I saw a lot of added this headers:

res.header("Access-Control-Allow-Headers","*");
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');

but i dont need that,

b) when you definer cookie you nee add httpOnly: false, like:

 res.cookie( key, value,{ maxAge: 1000 * 60 * 10, httpOnly: false });

c) client side: in send ajax you need add: "withCredentials: true," like:

$http({
     method: 'POST',
     url: 'url, 
     withCredentials: true,
     data : {}
   }).then(function(response){
        // code  
   }, function (response) {
         // code 
   });

good luck.

izik f
  • 1,885
  • 1
  • 14
  • 13
3

There's a few issues:

  • a cookie that isn't explicitly set with httpOnly : false will not be accessible through document.cookie in the browser. It will still be sent with HTTP requests, and if you check your browsers' dev tools you will most likely find the cookie there (in Chrome they can be found in the Resources tab of the dev tools);
  • the next() that you're calling should only be used if you want to defer sending back a response to some other part of your application, which—judging by your code—is not what you want.

So, it seems to me that this should solve your problems:

res.cookie('token', jwt.token, {
  expires  : new Date(Date.now() + 9999999),
  httpOnly : false
});
res.status(200).send({ user, token: jwt.token });

As a side note: there's a reason for httpOnly defaulting to true (to prevent malicious XSS scripts from accessing session cookies and the like). If you don't have a very good reason to be able to access the cookie through client-side JS, don't set it to false.

robertklep
  • 174,329
  • 29
  • 336
  • 330
  • 3
    I already tried to use this code (see first post) but _Resources - Cookie_ tab of the dev tools is still empty. I don't know why cookie isn't saved in browser :( – Mira Apr 26 '16 at 10:38
3

Double check the size of your cookie.

For me, the way I was generating an auth token to store in my cookie, was causing the size of the cookie to increase with subsequent login attempts, eventually causing the browser to not set the cookie because it's too big.

Browser cookie size cheat sheet

Dince12
  • 4,798
  • 3
  • 12
  • 32
1

There is no problem to set "httpOnly" to true in a cookie.

I am using "request-promise" for requests and the client is a "React" app, but the technology doesn't matter. The request is:

    var options = {
        uri: 'http://localhost:4000/some-route',
        method: 'POST',
        withCredentials: true
    }

    request(options)
        .then(function (response) {
            console.log(response)
        })
        .catch(function (err) {
            console.log(err)
        });

The response on the node.js (express) server is:

   var token=JSON.stringify({
  "token":"some token content"
});
res.header('Access-Control-Allow-Origin', "http://127.0.0.1:3000");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header( 'Access-Control-Allow-Credentials',true);
var date = new Date();
var tokenExpire = date.setTime(date.getTime() + (360 * 1000));
res.status(201)
   .cookie('token', token, { maxAge: tokenExpire, httpOnly: true })
   .send();

The client make a request, the server set the cookie , the browser (client) receive it (you can see it in "Application tab on the dev tools") and then I again launch a request to the server and the cookie is located in the request: "req.headers.cookie" so accessible by the server for verifying.

Pavel Aslanov
  • 160
  • 2
  • 6
1

I had the same issue with cross origin requests, here is how I fixed it. You need to specifically tell browser to allow credentials. With axios, you can specify it to allow credentials on every request like axios.defaults.withCredentials = true however this will be blocked by CORS policy and you need to specify credentials is true on your api like

const corsOptions = {
    credentials: true,
    ///..other options
  };

app.use(cors(corsOptions));

Update: this only work on localhost For detail answer on issues in production environment, see my answer here

0

A cookie can't be set if the client and server are on different domains. Different sub-domains is doable but not different domains and not different ports.

If using Angular as your frontend you can simply send all requests to the same domain as your Angular app (so the app is sending all API requests to itself) and stick an /api/ in every HTTP API request URL - usually configured in your environment.ts file:

export const environment = {
  production: false,
  httpPhp: 'http://localhost:4200/api'
}

Then all HTTP requests will use environment.httpPhp + '/rest/of/path'

Then you can proxy those requests by creating proxy.conf.json as follows:

{
  "/api/*": {
    "target": "http://localhost:5200",
    "secure": false,
    "changeOrigin": true,
    "pathRewrite": {
      "^/api": ""
    }
  }
}

Then add this to ng serve:

ng serve -o --proxy-config proxy.conf.json

Then restart your app and it should all work, assuming that your server is actually using Set-Cookie in the HTTP response headers. (Note, on a diff domain you won't even see the Set-Cookie response header, even if the server is configured correctly).

danday74
  • 38,089
  • 29
  • 169
  • 214
0

app.post('/api/user/login',(req,res)=>{

    User.findOne({'email':req.body.email},(err,user)=>{
        if(!user) res.json({message: 'Auth failed, user not found'})
        
        user.comparePassword(req.body.password,(err,isMatch)=>{
            if(err) throw err;
            if(!isMatch) return res.status(400).json({
                message:'Wrong password'
            });
            user.generateToken((err,user)=>{
                if(err) return res.status(400).send(err);
                res.cookie('auth',user.token).send('ok')
            })
        }) 
    })
});

response

res.cookie('auth',user.token).send('ok')

server gives response ok but the cookie is not stored in the browser

Solution :

Add Postman Interceptor Extension to chrome which allows postman to store cookie in browser and get back useing requests.

Prakash Karena
  • 2,133
  • 1
  • 3
  • 16
-1

One of the main features is to set header correctly.

For nginx:

add-header Access-Control-Allow-Origin' 'domain.com';

add_header 'Access-Control-Allow-Credentials' 'true';

Add this to your web server.

Then form cookie like this:

"cookie": {
        "secure": true, 
        "path": "/", 
        "httpOnly": true, 
        "hostOnly": true, 
        "sameSite": false, 
        "domain" : "domain.com"
    }

The best approach to get cookie from express is to use cookie-parser.

Ivan Vovk
  • 633
  • 9
  • 19