$http response Set-Cookie not accessible

2019-01-16 17:07发布

问题:

I'm currently writing an angularjs frontend to my backend, and I'm running into a somewhat common issue:

Server sends a cookie back in a response, but is completely ignored by angular.js (can't even print out the 'Set-Cookie' value).

I've tried reading

Set-Cookie in HTTP header is ignored with AngularJS

Angularjs $http does not seem to understand "Set-Cookie" in the response

but unfortunately I've tried all the solutions there and just doesn't seem to work.

Request sent

Response received

I'm using angular.js (v1.2.10), and this is what I used to make the request

$http.post('/services/api/emailLogin',
           sanitizeCredentials(credentials), 
           {
              withCredentials: true
           }).success(function(data, status, header) {
                console.log(header('Server'));
                console.log(header('Set-Cookie'));
                console.log(header('Access-Control-Allow-Headers'));
                console.log(header('Access-Control-Allow-Methods'));
                console.log(header);
            }).then(
                function(response) {
                    console.log(response);
                    return response.data;
                });

withCredentials=true is set on the client side before making the request.

Access-Control-Allow-Credentials=true is set on the server side before returning the response.

You can clearly see Set-Cookie is in the response headers from Chrome Developer Tools, but printout is just

Only Set-Cookie in the response header is not being printed out. I'm wondering why does this occur? Is there a way for me to make sure withCredentials=true is indeed set (I didn't see it in the request header)?

Any help is appreciated!

回答1:

I looked inside $httpBackend source code:

xhr.onreadystatechange = function() {

  // some code

  if (xhr && xhr.readyState == 4) {
    var responseHeaders = null,
        response = null;

    if(status !== ABORTED) {
      responseHeaders = xhr.getAllResponseHeaders();

      // some code

It uses XMLHttpRequest#getAllResponseHeaders to get the response headers.

After a little search I found this question: xmlHttp.getResponseHeader + Not working for CORS

Which made me realize that XHR by it's specification, doesn't support SET-COOKIE, so it have nothing to do with angular.js in particular.

http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders%28%29-method

4.7.4 The getAllResponseHeaders() method

Returns all headers from the response, with the exception of those whose field name is Set-Cookie or Set-Cookie2.


Instead just use $cookies:

It's a different module so you must add it to your scripts

 <script src="angular.js"></script>
 <script src="angular-cookies.js"></script>

And add it to the module dependencies:

 var app = angular.module('app',['ngCookies']);

Then just use it like so:

app.controller('ctrl',  function($scope, $http , $cookies, $timeout){

  $scope.request = function(){

    $http.get('/api').then(function(response){
      $timeout(function(){
        console.log($cookies.session)
      });         
    })
  }
})

I use $timeout because $cookies only synchronize with browser's cookies after a digest.



回答2:

Do you actually need to read the cookie in Angular, or is it enough if it gets set and passed back on further requests by the browser? While I wasn't able to get the former working, I was able to get the latter working pretty quickly with a very similar configuration. (In particular, I was not able to use $cookies as @Ilan recommended. It returned nothing.)

First things first, on the Angular side configure the $httpProvider to send withCredentials by default:

app.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.withCredentials = true;
}]);

That's all you have to do in Angular. Note that, at this point, if you attempt to read $cookies it will still not be able to see the cookie, but it is saved and will be passed with future AJAX calls.

Then, on the server, you need to provide a specific domain (or a set of domains) allowed under CORS, along with the Access-Control-Allow-Credentials header. My backend is Flask in Python with Flask-Restful and it looks like this:

import Flask
from flask_restful import Api
app = Flask(__name__)
api = Api(app)
api.decorators = [cors.crossdomain(origin='http://localhost:8100', credentials=True)]

That's all it took. Now Angular (or, rather, the browser) sets the cookie and returns it with future requests completely transparently!

(The same point is made here: https://stackoverflow.com/a/19384207/2397068.)



回答3:

In Angular 1.3.14 it doesn't work anymore.

I had the same problem. I am using $resource and declared withCredentials: true.

 var fooRes = $resource('/address/exemple',
    {},
    {
        bar: {
            method: 'POST',
            withCredentials: true,
        }
    }
 );

 fooRes.bar({
     "stuff" : "lorem"
 }).$promise.then(function(){
     //callback
 })

or you can set the header "Access-Control-Allow-Credentials", "true".

Now the cookie will be saved and you can get using $cookie.

Hope it will help.



回答4:

You need to modify your .htaccess file to allow the headers to be read.

Header set Access-Control-Allow-Headers Content-Type