$Http Doesn't Send Cookie in Requests

$http doesn't send cookie in Requests

In your config, DI $httpProvider and then set withCredentials to true:

.config(function ($httpProvider) {
$httpProvider.defaults.withCredentials = true;
//rest of route code
})

Info on angularjs withCredentials: http://docs.angularjs.org/api/ng.$http

Which links to the mozilla article: https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control#section_5

Set cookies for cross origin requests

Cross site approach

To allow receiving & sending cookies by a CORS request successfully, do the following.

Back-end (server):
Set the HTTP header Access-Control-Allow-Credentials value to true.
Also, make sure the HTTP headers Access-Control-Allow-Origin and Access-Control-Allow-Headers are set and not with a wildcard *.

For more info on setting CORS in express js read the docs here.

Cookie settings:
Cookie settings per Chrome and Firefox update in 2021: SameSite=None and Secure. When doing SameSite=None, setting Secure is a requirement. See docs on SameSite and on requirement of Secure. Also note that Chrome devtools now have improved filtering and highlighting of problems with cookies in the Network tab and Application tab.

Front-end (client): Set the XMLHttpRequest.withCredentials flag to true, this can be achieved in different ways depending on the request-response library used:

  • jQuery 1.5.1 xhrFields: {withCredentials: true}

  • ES6 fetch() credentials: 'include'

  • axios: withCredentials: true

Proxy approach

Avoid having to do cross site (CORS) stuff altogether. You can achieve this with a proxy. Simply send all traffic to the same top level domain name and route using DNS (subdomain) and/or load balancing. With Nginx this is relatively little effort.

This approach is a perfect marriage with JAMStack. JAMStack dictates API and Webapp code to be completely decoupled by design. More and more users block 3rd party cookies. If API and Webapp can easily be served on the same host, the 3rd party problem (cross site / CORS) dissolves. Read about JAMStack here or here.

Sidenote

It turned out that Chrome won't set the cookie if the domain contains a port. Setting it for localhost (without port) is not a problem. Many thanks to Erwin for this tip!

Angular HttpClient does not send domain cookie

XHR requests in Angular by default do not pass cookie information with each request. What this means is by default Angular doesn't pass Cookies captured on previous requests back to the server which effectively logs out the user.

And your server response must allow headers Access-Control-Allow-Credentials.

In order for that to work the HttpClient has to set the withCredentials:

CORS - Allow-Origin-With-Credentials

In addition to the client side withCredentials header, if you are going cross domain also make sure that the Allow-Origin-With-Credentials header is set on the server. If this header is not set the client side withCredentials also has no effect on cross-domain calls causing cookies and auth headers to not be sent.

let options = new RequestOptions({ headers: headers, withCredentials: true });
this.http.post(this.url, body , options);

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

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

Sample Image

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

Why the browser doesn't send cookies while requesting a JavaScript file?

Sorry, it was my mistake. Google Chrome was blocking third-party cookies.

By default browser send cookies with JavaScript file request.

How to tell why a cookie is not being sent?

This is a Chrome specific bug. No fix yet..

#56211 chrome.cookies fails for localhost domains

https://code.google.com/p/chromium/issues/detail?id=56211

May also want to read this question. It isn't specific to chrome like this question, but it is specific to localhost behavior (unlike this question).

angularjs $http service doesn't set cookie on POST but does with GET

I finally figured it out:

1) after comparison of working GET request with failing POST, I noticed that GET wasn't preflighted with OPTIONS, while POST was

2) in the CORS definig materials I read that OPTIONS are not sending any cookies

As a resolution I added checking request method in my DelegatingHandler - so it's passing OPTIONS methods down into pipeline without checking for autentifiaction cookie.

Hope that will help someone :)

Here's my WebApi DelegatingHandler:

public class UserCookieHandler : DelegatingHandler
{
static public string ServerSessionId = "server-session-id";
static public string UserSessionId = "user-session-id";
const string LoginUri = "api/secured/login/login";

async protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string sessionId = string.Empty;

if (request.Method != HttpMethod.Options && !request.RequestUri.PathAndQuery.Contains(LoginUri))
{
string cookie = request.GetCookie(ServerSessionId);

if (cookie == null)
{
return request.CreateErrorResponse(HttpStatusCode.Forbidden, "Please log in");
}
else
{
sessionId = cookie;
try
{
Guid guid = Guid.Parse(sessionId);
}
catch (FormatException)
{
request.CreateErrorResponse(HttpStatusCode.BadRequest, "Please log in again");
}
}
request.Properties[ServerSessionId] = sessionId;

}
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

return response;

}

}


Related Topics



Leave a reply



Submit