Mira
Mira

Reputation: 651

Express doesn't set a cookie

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?

Upvotes: 64

Views: 138363

Answers (22)

Sapthaka
Sapthaka

Reputation: 420

If you use Axios, do this,

export const authApi = axios.create({
  baseURL: authApiURL,
  headers: {
    'Content-Type': 'application/json',
  },
  withCredentials: true,  
});

authApi.post('/login', userData)

As you can see, withCredential field should be set to true. Then browser will save the cookie.

If you use fetch, do the same thing. But this time use

credentials:true

instead of withCredentials.

Upvotes: 0

AJB
AJB

Reputation: 37

First install npm packages

npm i jsonwebtoken cookie-parser

Require it to a variable

const cookieParser = require("cookie-parser")
const jwt = require("jsonwebtoken");

In app.js

app.use(cookieParser())

// JWT Creator

const createToken = (id) => {
  return jwt.sign({ id }, "usertoken", { expiresIn: 1 * 24 * 60 * 60 });
};
const token = createToken(user._id);
    res.cookie("jwt", token).status(200).json({
      success: true,
      
    });

To check the cookie:- right click -> inspect or ctrl+shift+I go to application (tab) -> Cookies -> check for 'jwt' in name you are done !

Upvotes: 0

wait_walker
wait_walker

Reputation: 21

In the Fetch which you are using to post to Backend you will have to add a column i.e credentials:'include', like this:-

method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*",
    },
    credentials: "include",
    body: JSON.stringify({ parameters }),

also in the Backend specify the URL. Let's say the frontend is running on localhost:3000 so specify it in CORS origin. like this:-

cors({
origin: "http://localhost:3000",
credentials: true,
 })

and then to set the cookie:-

 res.cookie("token", token, {
          httpOnly: true,
          expires: new Date(Date.now() + 99* 99* 99 * 60000),
        });

This should solve the problem, to view if the cookie is set or not go to Developer console -> applications -> cookies

Upvotes: 0

Akash Paloju
Akash Paloju

Reputation: 1

In 2023, I solved this issue by adding small things in the code in my node-react app.

a) react code. in axios request. add withCredentials : true

const handleSubmit = async (e) => {
    try {
      
      e.preventDefault() ;
      const response = await axios.post(`${API_URLS.LOGIN}`,formData, { withCredentials: true }) ;
      // other code
      navigate("/") ; // to home page

    } catch (error) {
      console.log(error);
    }
  }

b)in node, in main file like app.js, add corsOptions,

const corsOptions = {
  origin: "http://localhost:3000", // to allow requests from client
  credentials: true,
};
app.use(cors(corsOptions));   

Upvotes: 0

D-D
D-D

Reputation: 11

I fix this issue by setting withCredentials to true on the client side fetch API request

const options = {
            url: '/your-url',
            method: 'POST',
            headers: {
              'Accept': 'application/json',
              'Content-Type': 'application/json'
            },
            credentials: 'include', 
            withCredentials: true, // You need to specify this if you are using cookies
            data: {
                yourData: yourDataValue
            }
          };

Upvotes: 1

AstroKin
AstroKin

Reputation: 11

vue axios + node express 2023

server.ts (backend)

const corsOptions = {
  origin:'your_domain',
  credentials: true,
  optionSuccessStatus: 200,
}

auth.ts (backend)

res.cookie('token', JSON.stringify(jwtToken), {
  secure: true,
  httpOnly: true,
  expires: dayjs().add(30, "days").toDate(),
  sameSite: 'none'

})

authService.ts (frontend)

export class AuthService {
  INSTANCE = axios.create({
    withCredentials: true,
    baseURL: 'your_base_url'
  })

  public Login = async (value: any): Promise<void> => {
    try {
      await this.INSTANCE.post('login', { data: value })

      console.log('success')
    } catch (error) {
      console.log(error)
  }
}

it works for me, the cookie is set, it is visible from fn+F12 / Application / Cookies and it is inaccessible with javascript and the document.cookie function. Screenshot Cookies Browser

Upvotes: 1

Redskinsjo
Redskinsjo

Reputation: 452

You have to combine:

  • including credentials on the request with, for example withCredentials: true when using axios.
  • including credentials on the api with, for example credentials: true when using cors() mw.
  • including the origin of your request on the api, for example origin: http://localhost:3000 when using cors() mw.

Upvotes: 0

izik f
izik f

Reputation: 2467

I work with express 4 and node 7.4 and Angular, I had the same problem this helped me:

a) server side: in file app.js I give headers to all responses 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 routers.
I saw a lot of added this header:

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 don't need that.

b) when you define cookie you need to add httpOnly: false, like:

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

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

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

Upvotes: 16

SMEETT
SMEETT

Reputation: 217

I am late to the party but nothing fixed it for me. This is what I was missing (and yeah, it's stupid):

I had to add res.send() after res.cookie() - so apperently sending a cookie is not enough to send a response to the browser.

res.cookie("testcookie", "text", cookieOptions);
res.send();

Upvotes: 2

smit agravat
smit agravat

Reputation: 295

I struggle with it a lot so follow below solution to get through this

1 check if you are getting token with response with postmen in my case i was getting token in postmen but it wasn't being saved in cookies.

I was using a custom publicRequest which looks like below

  try {
    const response = await publicRequest.post("/auth/login", user, {withCredentials: true});
    dispatch(loginSuccess(response.data));
  } catch (error) {
    dispatch(loginFail());
    dispatch(reset());
  }

I was using this method in other file to handle login

I added {withCredentials: true} in both methods as option and it worked for me.

Upvotes: 0

Green
Green

Reputation: 30785

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'
    })
})

Upvotes: 134

mdmostafa
mdmostafa

Reputation: 852

I had same problem in Angular application. The cookies was not set in browser although I used

res.cookie("auth", token, {
  httpOnly: true,
  sameSite: true,
  signed: true,
  maxAge: 24 * 60 * 60 * 1000,
});

To solve this issue, I added app.use(cors({ origin:true, credentials:true })); in app.js file of server side

And in my order service of Angular client side, I added {withCredentials: true} as a second parameter when http methods are called like following the code

getMyOrders() {
return this.http
  .get<IOrderResponse[]>(this.SERVER_URL + '/orders/user/my-orders', {withCredentials: true})
  .toPromise();}

Upvotes: 1

Azibodusi Duke Osain
Azibodusi Duke Osain

Reputation: 11

Most of these answers provided are corrections, but either of the configuration you made, cookies won't easily be set from different domain. In this answer am assuming that you are still in local development.

To set a cookie, you can easily use any of the above configurations or

  res.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']); // setting multiple  cookies or 
          res.cookie('token', { maxAge: 5666666, httpOnly: true })

Both of the will set your cookie while to accessing your cookie from incoming request req.headers. In my case, my cookie were not setting because my server was running on http://localhost:7000/ while the frontend was running on http://127.0.0.1:3000/ so the simple fix was made by making the frontend run on http://localhost:3000 instead.

Upvotes: 0

gaurav deshwal
gaurav deshwal

Reputation: 101

I was also going through the same issue.

Did code changes at two place :

  1. At client side :
   const apiData = await fetch("http://localhost:5000/user/login", 
{
        method: "POST",
        body: JSON.stringify(this.state),
        credentials: "include", // added this part
        headers: {
          "Content-Type": "application/json",
        },
      })
  1. And at back end:
   const corsOptions = {
    origin: true, //included origin as true
    credentials: true, //included credentials as true
};

app.use(cors(corsOptions));

Upvotes: 10

Abdullah Oladipo
Abdullah Oladipo

Reputation: 350

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

Upvotes: 6

DedaDev
DedaDev

Reputation: 5249

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;

Upvotes: 24

Pavel Aslanov
Pavel Aslanov

Reputation: 180

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.

Upvotes: 1

Prakash Karena
Prakash Karena

Reputation: 2595

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.

Upvotes: -1

danday74
danday74

Reputation: 56936

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).

Upvotes: 1

Mr Derfenstien
Mr Derfenstien

Reputation: 31

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

Upvotes: 3

Ivan Vovk
Ivan Vovk

Reputation: 1039

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.

Upvotes: 0

robertklep
robertklep

Reputation: 203231

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.

Upvotes: 5

Related Questions