Set-Cookie in HTTP header is ignored with AngularJS

82,975

Solution 1

I found an issue in AngularJS that help me to move forward.

It seems that "Access-Control-Allow-Credentials" : true was not set on the client side. Instruction $httpProvider.defaults.withCredentials = true was ignored.

I replace $resource call by a simple $http call with {withCredentials:true} in the config parameter.

Solution 2

I've managed to solve an issue very similar to yours. My Play! backend tried to set a session Cookie which I could not catch in Angular or store via browser.

Actually the solution involved a bit of this and a bit of that.

Assuming you've solved the initial issue, which can be solved only by adding a specific domain to the Access-Control-Allow-Origin and removing the wildcard, the next steps are:

  1. You have to remove the HTTP-Only from the Set-Cookie header, otherwise you will never be able to receive a cookie "generated" by your angular code
    This setup will already work in Firefox, though not in Chrome

  2. To make it work for Chrome too, you need to:

    a) send a different domain from localhost in the cookie, using the domain your WS are "hosted". You can even use wildcards like .domain.com instead of ws.domain.com

    b) then you'll need to make a call to the domain you specified in the cookie, otherwise Chrome won't store your cookie

    [optional] I would remove that /api path in favor of a /


And that should to the trick.
Hope to have been of some help

Solution 3

In your post request on the client side, make sure to add the following:

For jquery ajax requests:

$.ajax({
  url: "http://yoururlgoeshere",
  type: "post",
  data: "somedata",
  xhrFields: {
    withCredentials: true
  }
});

With Angular's $http service :

$http.post("http://yoururlgoeshere", "somedata", {
  withCredentials: true
});

Solution 4

You need work on both the server and client side.

Client

Set $http config withCredentials to true in one of the following ways:

  1. Per request

    var config = {withCredentials: true};
    $http.post(url, config);
    
  2. For all requests

    angular.module("your_module_name").config(['$httpProvider',
      function($httpProvider) {
        $httpProvider.interceptors.push(['$q',
          function($q) {
            return {
              request: function(config) {
                config.withCredentials = true;
                return config;
              }
            };
          }
        ]);
      }
    ]);
    

Server

Set the response header Access-Control-Allow-Credentials to true.

Solution 5

The addition HttpOnly means that the browser should not let plugins and JavaScript see the cookie. This is a recent convention for securer browsing. Should be used for J_SESSIONID but maybe not here.

Share:
82,975
Romain Lefrancois
Author by

Romain Lefrancois

Updated on August 03, 2020

Comments

  • Romain Lefrancois
    Romain Lefrancois almost 4 years

    I'm working on an application based on AngularJS on client side and Java for my API (Tomcat + Jersey for WS) on server side.

    Some path of my API are restricted, if the user doesn't have a session the response status returned is 401. On the client side, 401 http status are intercepted to redirect the user to the login page.

    Once the user is authenticated, I create a session on the server side

    httpRequest.getSession(true);
    and the response send to the client does have the Set-cookie instruction in its header :

    Set-Cookie:JSESSIONID=XXXXXXXXXXXXXXXXXXXXX; Domain=localhost; Path=/api/; HttpOnly
    

    The problem is that the cookie is never put on the client side. When I inspect cookie for localhost domain it's empty, so the next requests don't have this cookie in their header and client side still couldn't access to the restricted path of my API.

    The client and the server are on the same domain but they don't have the same path and the same port number :

    Client : http://localhost:8000/app/index.html

    Server : http://localhost:8080/api/restricted/

    Additional info : CORS is enabled on the both side :

    "Access-Control-Allow-Methods", "GET, POST, OPTIONS"
    "Access-Control-Allow-Origin", "*"
    "Access-Control-Allow-Credentials", true

    Any idea for making the Set-cookie works properly ? Is it an AngularJS related issue ?

  • Romain Lefrancois
    Romain Lefrancois about 11 years
    Even if the HttpOnly attribute is not present, the cookie is still not set on the client side.
  • Joop Eggen
    Joop Eggen about 11 years
    Are you sure? It is a session-only cookie for /api/ only. One experiment is to use response.encodeURL for your links. The first answer might then still use "...?JSESSIONID=..." instead of the cookie. But subsequent calls should have the cookie set. Did you look in the browser for the cookies?
  • Romain Lefrancois
    Romain Lefrancois about 11 years
    Yes, when I use Chrome to look if the cookie is set in the browser and/or present in request and response header. The cookie is Session only yes.
  • Joop Eggen
    Joop Eggen about 11 years
    I do not know further if the cookie for localhost/api is not set. You could try to remove the HttpOnly (would be interesting), or set the cookie for localhost/ instead of localhost/api (unlikely).
  • Romain Lefrancois
    Romain Lefrancois about 11 years
    I already try both of this solution but no results ... Btw, thanks for your help.
  • Ovidiu Buligan
    Ovidiu Buligan about 11 years
    Don't forget to post the answer if you figured out your solution. If your client is mobile and you are using phonegap/cordova or a native client did you try to override the origin/refferer headers and use a constant origin?
  • Seth M.
    Seth M. over 10 years
    I believe the problem is that path being /api and should be /
  • Pathsofdesign
    Pathsofdesign about 10 years
    When you say "send a different domain from localhost in the cookie, using the domain your WS are "hosted"" do you mean the front-end host or the backend host?
  • domokun
    domokun about 10 years
    Usually it should be your backend, but it really depends on your server architecture. Don't you know where your WS are?
  • Lauris
    Lauris almost 10 years
    You can get origin in your backend and then send a relevant header. Here is an express.js example: var origin = req.headers.origin; res.header('Access-Control-Allow-Origin', origin);
  • vs4vijay
    vs4vijay almost 9 years
    No need to create interceptor, now you can use this $httpProvider.defaults.withCredentials = true;
  • elixenide
    elixenide over 8 years
    You appear to have posted both an answer and a question as an answer. To ask a new question, use the "Ask Question" button at the top of the page. You can link to this question if it helps provide context.
  • David
    David over 8 years
    In addition to HttpOnly removal from set-cookie, we should have to remove/change Path part of set-cookie to Path=/; then it looks like this: Set-Cookie:JSESSIONID=XXXXXXXXXXXXXXXXXXXXX; Domain=localhost; All of this is done in node.js server using http-proxy and passing only /api calls to backend server.
  • David
    David over 8 years
    Also remove Secure; (if it is presented) from Set-Cookie if you want your browser to be able to store and use cookies in subsequent XHR calls.
  • Jan Weitz
    Jan Weitz over 7 years
    You have to explicitly return the correct domain for "Access-Control-Allow-Origin", "" when "Access-Control-Allow-Credentials", true. An asterisk is not allowed. So what people do to have multiple allowed origins is to have a function intercept the request origin, validate, if this origin is in your allowed list, and set the request origin in "Access-Control-Allow-Origin". e.g. I want localhost and example.com to work. So when a request comes in from localhost, my header must be "Access-Control-Allow-Origin", "localhost", for example.com "Access-Control-Allow-Origin", "example.com"
  • medBouzid
    medBouzid over 6 years
    I found out in my case that xhrFields:{...} cause some issue (my server respond with something like unknow origin *), so I used this instead beforeSend: function(xhr){ xhr.withCredentials = true; } and it works fine
  • 0xC0DEGURU
    0xC0DEGURU about 4 years
    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 set 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.
  • 0xC0DEGURU
    0xC0DEGURU about 4 years
    Secure means don't attach cookies to a http request (including ajax) when the protocol is not https://. HttpOnly means that Javascript cannot access cookie through document.cookie, but anyway it will send it along with an ajax request.
  • 0xC0DEGURU
    0xC0DEGURU about 4 years
    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 set 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 a 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 won't save it.