user1242321
user1242321

Reputation: 1696

Unable to fetch cookies, set by Play framework server, in Angular $http call?

I have been trying to get hold of the cookies I am setting in the server using PlayFramework:

response().setHeader(SET_COOKIE, AppConstants.COOKIE_USER_SESSIONID+"="+appSession.getSid());

in the angular app, but am not able to.

If I use the Advanced Rest client, I get the SetCookie header and the cookies get set in the call that follows. However when I call the same api through my angular app, I am not able to get the header in the response and hence no cookies for the app.

Here's what I have already tried:

  1. renaming the environment from localhost to xyz.com since I read on multiple answers that cookies do not work at localhost.
  2. Tried the api form a separate rest client, was able to fetch the header successfully, so the API looks fine to me.

There should be some catch in angular code which I am not able to figure out. Any help on this is highly appreciated. Thanks.

Angular code snippets:

LoginController:

angular.module('wratApp')
  .controller('LoginCtrl', function (postService, wratSettings, wratRoutes, $location, $cookies) {
    $("#header").hide();

    this.user= {};

    this.logUserin = function(){
        console.log("User: " + this.user.email + " & pwd: " + this.user.pwd);
        var payLoad = {};
        payLoad.ldap = this.user.email;
        payLoad.pwd = this.user.pwd;
        postService.postPromise(wratRoutes(wratSettings).POST_USER_LOGIN(),payLoad)
          .then(function(){ //login success
              console.log("login success");
              $location.path(wratRoutes().CLIENT_HOME());
          }, function(){ //error in login
              console.log("Login failed");
          })
        ;
    }

  });

postService:

angular.module('wratApp')
  .factory('postService', function ($http, $q, $cookies, $location) {

    function postService() {

      var self = this;

      self.postPromise = function (uri,payload){

        var deferred = $q.defer();

        $http.post(uri,payload)
          .success(function(data, status, headers, config){
            console.log("Got response cookies: "+$cookies.getAll());
            deferred.resolve(data);
          })
          .error(function(data, status, headers, config){
            if(status == 401){
              angular.forEach($cookies.getAll(), function (v, k) {
                $cookies.remove(k);
              });
              $location.path('/login');
            }else{
              deferred.reject(data);
            }
          });

        return deferred.promise;

      }

    }

    return new postService();
  });

Now, my login succeeds but without any cookies being set by the server. Also, the PlaySession cookie which is somehow visible in the debugger(the manually set ones are even absent from debugger), is not in the angular $cookies variable(refer image below).

enter image description here

Please suggest how can I resolve this. Thanks.

Update 1:

If I run the Play server not in debug mode, the cookies are appropriately being sent by the server. It's an issue with the angular app where in the following call, it's not transferring the values form the Set-Cookie header to the cookies for the next call. It might be some silly mistake on my end too. Please see if you can help me figure out.

Server response /login:

HTTP/1.1 200 OK
Vary: Origin
Set-Cookie: sid=1e6823e24554598b0521ca5f64d7746b; Path=/
Set-Cookie: PLAY_SESSION=953d9d5730bf1b77cccaadef6b78c209e59d924b-sid=1e6823e24554598b0521ca5f64d7746b; Path=/; HTTPOnly
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://wrat.com:9009
Access-Control-Allow-Methods: OPTIONS, GET, POST
Access-Control-Allow-Credentials: true
Date: Thu, 24 Sep 2015 22:27:03 GMT
Content-Length: 429

The following request which should have cookies set, but no cookies there :-( :

GET /products/all HTTP/1.1
Host: wrat.com:9000
Accept: application/json, text/plain, */*
Origin: http://wrat.com:9009
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36
Referer: http://wrat.com:9009/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8

Taking off the Play framework tag since it's not a play issue anymore.

Upvotes: 0

Views: 795

Answers (1)

user1242321
user1242321

Reputation: 1696

Adding the withCredentials option to the $http calls worked for me and resolved the issue.

$http.post(uri,payload, {withCredentials: true})

The issue is with the ajax calls where if this withCredentials flag is not truthy at both server and client, the cookies set by server are not maintained for cross-origin calls. Hence it was totally ignoring the cookies I was setting from the server.

Text from following question helped resolve the problem:

Why is jquery's .ajax() method not sending my session cookie?

from:

I am operating in cross-domain scenario. During login remote server is returning Set-Cookie header along with Allow-Access-Control-Credentials set to true.

The next ajax call to remote server should use this cookie.

CORS's Access-Control-Allow-Credentials is there to allow cross-domain logging. Check https://developer.mozilla.org/En/HTTP_access_control for examples.

For me it seems like a bug in JQuery (or at least feature-to-be in next version).

UPDATE:

Cookies are not set automatically from AJAX response (citation: http://aleembawany.com/2006/11/14/anatomy-of-a-well-designed-ajax-login-experience/)

Why?

You cannot get value of the cookie from response to set it manually (http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-getresponseheader)

I'm confused..

There should exist a way to ask jquery.ajax() to set XMLHttpRequest.withCredentials = "true" parameter.

ANSWER: You should use xhrFields param of http://api.jquery.com/jQuery.ajax/

The example in the documentation is:

$.ajax({ url: a_cross_domain_url, xhrFields: { withCredentials: true } }); It's important as well that server answers correctly to this request. Copying here great comments from @Frédéric and @Pebbl:

Important note: when responding to a credentialed request, server must specify a domain, and cannot use wild carding. The above example would fail if the header was wildcarded as: Access-Control-Allow-Origin: *

So when the request is:

Origin: http://foo.example Cookie: pageAccess=2 Server should respond with:

Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Credentials: true

[payload] Otherwise payload won't be returned to script. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials

Upvotes: 1

Related Questions