25

I have a nodejs express REST api with Passport module for authentication. A login method (GET) returns a cookie in the header. When I call it from Chrome it works fine, my cookie is set in my browser.

But if I call it through $http from Angularjs, the cookie is not set.

Set-Cookie:connect.sid=s%3Ad7cZf3DSnz-IbLA_eNjQr-YR.R%2FytSJyd9cEhX%2BTBkmAQ6WFcEHAuPJjdXk3oq3YyFfI; Path=/; HttpOnly

As you can see above, the Set-Cookie is present in the header of the http service response.

Perhaps HttpOnly may be the source of this problem? If yes, how can I change it? Here is my express configuration :

app.configure(function () {
    app.use(allowCrossDomain);
    app.use(express.bodyParser());
    app.use(express.cookieParser())
    app.use(express.session({ secret: 'this is a secret' }));
    app.use(flash());
    //passport init
    app.use(passport.initialize());
    app.use(passport.session());
    app.set('port', process.env.PORT || 8080);
});

Thank you for your help

Fred Mériot
  • 4,157
  • 9
  • 33
  • 50
  • could you provide your angular method that call $http for login in please. can't tell what's wrong for now – Ostro Oct 15 '13 at 14:14

5 Answers5

19

Make sure to configure your $http request to use credentials. From the XHR documentation:

https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS

The most interesting capability exposed by both XMLHttpRequest and Access Control is the ability to make "credentialed" requests that are cognizant of HTTP Cookies and HTTP Authentication information. By default, in cross-site XMLHttpRequest invocations, browsers will not send credentials. A specific flag has to be set on the XMLHttpRequest object when it is invoked.

You can use the options object to set the use of credentials, see

http://docs.angularjs.org/api/ng.$http

Example:

$http({withCredentials: true, ...}).get(...)
kapex
  • 28,903
  • 6
  • 107
  • 121
Thomas
  • 11,272
  • 2
  • 24
  • 40
  • 3
    But AngularJS still doesn't understand `set-cookie` in the response. – Melkor May 17 '15 at 07:47
  • I took me more than a day to realise that `withCredentials = true` not only tells browser to attach cookies on request, but also tells it to save new cookies on response. It's the same with ajax options for `fetch` function where `credentials: 'include'` must be applied to make browser sending and setting cookies on request and response. I.e. when sending `login` ajax request to a dedicated api, though no cookies are requiered, `withCredentials=true` must be set otherwise the received `sessionid` cookie will be available but the browser will not save it. – 0xC0DEGURU Apr 02 '20 at 13:22
5

How are you checking for the existence of the cookie?

$http is just a wrapper around the browser's XHR, just like jQuery's $.ajax and friends. That means your browser will handle the cookies normally.

My guess is that the cookie is being set, but you're trying to read it through document.cookie. HttpOnly prevents script from having access to the cookie. This is a good thing.

HttpOnly helps mitigate against XSS attacks. It makes it impossible for malicious script to steal a user's session token (and thus their login). Do not turn off HttpOnly.

You should not need the cookie anyway. Since $http is just a wrapper around XHR, the browser will automatically send the cookie the next time you make a request. You can verify this by watching the requests in the Developer Tools' Network panel.

josh3736
  • 139,160
  • 33
  • 216
  • 263
  • I'd edit the post to account for the specific requirement of setting withCredentials to true. It won't send cookies without that and also the Access-Control-Allow-Credentials header. Regarding the comments about HttpOnly that's a VERY good point +1 for making sure everything is secure. – Dan Sabin Jan 14 '16 at 23:30
5

I solved the problem by setting {withCredential : true} in the angular $http request and by setting :

res.header("Access-Control-Allow-Credentials", true);

in my express server configuration. By doing this, my cookie appears well in my browser cookies list.

Fred Mériot
  • 4,157
  • 9
  • 33
  • 50
2

Below solution worked for me

In Angular, I have used following code snippet using withCredentials: true for my request.

this._http.post(this._apiUrl + 'api/' + url, data, { withCredentials: true })
            .subscribe(data => { resolve(data) }, error => { this.showServerError(error)})

In Node, before I got the solution, I was just initializing CORS using

app.use(cors());

But, now I have implemented the following code snippet

app.use(cors({origin: [
   "http://localhost:5000"
 ], credentials: true}))

Where http://localhost:5000 is my Angular application endpoint. You can set this URL dynamically from config or .env file.

Hope that above solution will work for your problem.

Vatsal Shah
  • 1,334
  • 18
  • 21
0

You can change it by configuring express.session:

app.use(express.session({
  secret : 'this is a secret',
  cookie : {
    httpOnly : false,
    maxAge   : XXX // see text
  }
});

But I don't think Angular cares much about cookies, those are handled by the browser (and I agree with @josh3736 that you should keep httpOnly to true).

It might have something to do with your cookies being (browser-)session cookies. You can try and see if it works better by explicitly setting a maxAge for the cookie (in milliseconds).

robertklep
  • 198,204
  • 35
  • 394
  • 381