Angular is not sending the Cookie received in Set-Cookie even if withCredentials is true

Angular

Angular Problem Overview


For cookie based authentication, my server sends Set-Cookie to my Angular application. However, the application doesn't send the value back in further requests. Following is my code.

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
  withCredentials: true //this is required so that Angular returns the Cookies received from the server. The server sends cookies in Set-Cookie header. Without this, Angular will ignore the Set-Cookie header
};

public getUserProfile(){
    console.log('contacting server at '+this.API_URL +this.GET_USER_PROFILE_URL+"with httpOptions "+httpOptions);
    return this.http.get(this.GET_USER_PROFILE_URL,httpOptions )
      .map(response=>{
        console.log('response from backend service',response);
        let result= <ServerResponse>response; 
        console.log("result is "+result.result+' with additional information '+result.additionalInformation)
        return result;
      })
      .catch(this.handleError);
  }

The server sends the cookie as follows in 200OK of my code (not shown here)

Set-Cookie: id=...

The next message however hasn't got the id in the cookie, thus the server returns 401. If I manually add the Cookie using browser's debug tools, then I get 200OK. Thus I am certain it is the absence of the id value in the cookie which is causing the issue.

What am I doing wrong? Do I need to explicitly store the cookie received in Set-Cookie and explicitly add it in further requests?

Update - At the time when the SPA is initially loaded, the server sends Set-Cookie header with some other cookie's information related to CSRF. I notice that that cookie is still sent by the application. Could it be that Angular honors the first Set-Cookie header but ignores the subsequent ones?

I have added couple of pics to explain what I mean

During signing, the client sends a cookie related to CSRF. I dont think it is required as the client also sends CSRF Header but for some reason it does. The server responds with Set-Cookie with id in it

enter image description here

Then when I ask for profile, the client again sends the CSRF cookie but not the id cookie

enter image description here

Angular Solutions


Solution 1 - Angular

Finally, I was able to find the issue. The journey was more satisfying than the result so let me break it down into the steps I took to solve my problem.

In summary, This wasn't an issue with Angular. The cookie I was sending had secureCookie flag on. As I was testing my application without https, it seems that the angular application was not using (or getting access to) the Set-Cookie header received in 200 OK.

My initial code to send the sign in request to the server and handling its response was

return this.http.post(this.SIGNIN_USER_URL, body, httpOptions)
  .map(response => {
    console.log('response from backend service', response);
    let result= <ServerResponse>response; 
    console.log('result is ' + result.result + ' with additional information '+result.additionalInformation)
    return result;
  })
  .catch(this.handleError);

I wasn't using observe: 'response' option which meant that the response would only contain body, not headers. I changed the code to following so that I could see which headers are being received.

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
     
  withCredentials: true, 
  observe: 'response' as 'response'
};  
    
public signinUser(user: UserSigninInfo): any {
  console.log('contacting server at ' + this.API_URL + this.SIGNIN_USER_URL + " with user data " + user + " with httpOptions " + httpOptions.withCredentials + "," + httpOptions.headers ); 
    
  let signinInfo = new UserSignin(user);
  let body = JSON.stringify(signinInfo);
  return this.http.post(this.SIGNIN_USER_URL, body, httpOptions).catch(this.handleError);
}

The above code was being called as follows. I change that to get the headers in the response

return this.bs.signinUser(user).subscribe((res: HttpResponse<any>) => {console.log('response from server:', res);
  console.log('response headers', res.headers.keys())
});

I also created an intercepter to print the incoming and outgoing messages (copied from SO)

import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from "@angular/common/http";
import {Injectable} from "@angular/core";
import {Observable} from "rxjs/Observable";
import 'rxjs/add/operator/do';
    
@Injectable()
export class CustomInterceptor implements HttpInterceptor {
    
  constructor() {}
    
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    
    console.log("outgoing request",request);
    request = request.clone({ withCredentials: true });
      console.log("new outgoing request",request);
    
      return next
        .handle(request)
        .do((ev: HttpEvent<any>) => {
          console.log("got an event",ev)
          if (ev instanceof HttpResponse) {
            console.log('event of type response', ev);
          }
      });
  }
}

When I started debugging, I noticed that though the server was sending 10 headers, only 9 were getting printed

headers from server

enter image description here

Print message on console (Set-Cookie was missing! The header my application needed to get authentication cookie)

0: "Content-Length"
​
1: "Content-Security-Policy"2: "Content-Type"3: "Date"4: "Referrer-Policy"5: "X-Content-Type-Options"6: "X-Frame-Options"7: "X-Permitted-Cross-Domain-Policies"8: "X-XSS-Protection"length: 9

This gave me a direction that the application is not seeing Set-Cookie header. I thought I'll be able to resolve it by adding CORS policy in play framework exposedHeaders = ["Set-Cookie"] but that didnt work. Later I looked closer at the cookie and noticed secureCookie setting

Set-Cookie: id=...Secure; HTTPOnly

This made me think that maybe my cookie settings are wrong for my environment (localhost, no HTTPS). I changed the cookie settings in Silhoutte

val config =  CookieAuthenticatorSettings(secureCookie=false)

And it worked!

Though I'll make the above code work for secureCookie and this wasn't an issue with Angular, I hope that some folks might find the approach helpful

Solution 2 - Angular

"Set-Cookie" with a flag "HttpOnly" means you can not read the cookie from the client-side.

Solution 3 - Angular

i assume you using nodejs and express-session for manage session then in express-session httpOnly are by default enabled so you have to change httpOnly for console sever sent cookie

>const app = require('express')();
var session = require('express-session');
var cookieParser = require('cookie-parser');

> app.use(session( { secret:'hello world',
store:SessionStore,
resave:false,
cookie:{
secure:false,
httpOnly:false // by default it's boolean value true }
}));

in the below image httpOnly are true, so you can't be console this cookie. enter image description here

in the below image httpOnly are false , so you can console this cookie.enter image description here

Solution 4 - Angular

NOTE - REFER THIS ANSWER IF YOU ARE USING ZUUL IN PROJECT

Trying all possible solutions (referring articles and n number of stackoverflow questions) and banging head for 2 days, finally, I found this solution. Thanks to Zuul docs - Cookies and Sensitive Headers.

I was facing the same issue - from API response, set-Cookie response header was coming where as calling same api from Angular code, set-cookie was getting skipped or ignored.

Use 'sensativeHeaders' property inside zuul configuration in your application.yml or properties file. Refer below code snippet

zuul:
   routes:
      users:
      path: /myusers/**
      sensitiveHeaders:
      url: https://downstream

I have not set any extra headers or properties like 'withCredentials' in interceptor.

Solution 5 - Angular

we need to check one more option when getting set-cookies header,

set-cookie: SESSIONID=60B2E91C53B976B444144063; Path=/dev/api/abc; HttpOnly

Check for Path attribute value also. This should be the same as your API starting context path like below

https://www.example.com/dev/api/abc/v1/users/123

or use below value when not sure about context path

Path=/;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManu ChadhaView Question on Stackoverflow
Solution 1 - AngularManu ChadhaView Answer on Stackoverflow
Solution 2 - AngularMoamenView Answer on Stackoverflow
Solution 3 - AngularPiyush JainView Answer on Stackoverflow
Solution 4 - AngularMorezView Answer on Stackoverflow
Solution 5 - Angularsharad jainView Answer on Stackoverflow