WebSocket pass cookie to another domain when connecting - cookies

I own two domains: domain A and a domain B.
I want to setup websocket server on domain B and also API endpoint that returns cookie X.
I have called domain B to get that cookie, which is marked as Secure: true, SameSite: None.
The cookie was returned and set in the browser for domain B.
Now, I want to connect to websocket on domain B from domain A, but cookie X is not getting passed when using Chrome or Firefox (haven't tried other browser).
When I test it and call domain B from domain A using axios and withCredentials: true, it attaches the cookie properly.
I'm using built-in browser WebSocket API.
How to force browser to pass that cookie to domain B?

Related

Secure Cookies not working with hostname set in hosts file

I have cookies set as secure, but it's working as localhost. But with the domain name set in the hosts file, chrome is not sending the cookies to server resulted in request failing.
i am running my server on the localhost with the following cookie set, the cookie is being sent by chrome to my server.
cookie := http.Cookie{Name: "XSRF-TOKEN", Value: csrf.Token(r), Expires: expiration, Path: "/", HttpOnly: false, Secure: true}
I have set the following in my host file and trying to open from chrome x.test.in the cookie is not getting passed . chrome reporting as set-cookie was blocked because it has the secure attribute but was not received the following code is in golang and using gorilla mux
127.0.0.1 x.test.in
Did you call the server with HTTP or HTTPS? If the "Secure" Flag is set on the cookie it can only be set after an HTTPS call
If it was indeed blocked by Chrome you can still see it by going into the "Network" Tab of your developer tools, select the request, click on "Cookies" and select "show filtered out request cookies". The blocked cookie should appear and you should see there why this was the case.
You can try this in an other browser. I think chrome means, the cookie is a fake-secure cookie.

Cookie not being set on angular client

I have a backend app in django python and it is being served on http://localhost:8000.
I have a angular frontend which is being served on http://localhost:4200.
I have disabled CORS on django.
On hitting the login api on http://localhost:8000/auth/login/, I am getting a valid response
along with the Set-Cookie header.
Here is my angular code to print the cookies:
this.http.post<any>('http://localhost:8000/auth/login/', this.LoginForm, { observe: 'response' }).subscribe(response => {
console.log("response is ", response);
var cookies = this.cookieService.getAll();//('cookies');
console.log("cookies is :", cookies);
It prints an empty object on console.
How do I make this work? I want to use cookies for authentication.
You are trying to set cross domain cookies, which will not work straight away. There are a few steps to follow to be able to do that.
Set withCredentials: true when making the authentication request from angular
this.http.post<any>('http://localhost:8000/auth/login/', this.LoginForm, { observe: 'response', withCredentials: true })
Configure your server to return the following CORS headers: Access-Control-Allow-Credentials: true and Access-Control-Allow-Origin: http://localhost:4200
Note
One of the cookies that you are setting is HttpOnly. As such, you cannot access it from Javascript (see documentation).
You may not need to access the cookies with JS anyway. If you just want to send the cookies in the next API requests, just pass withCredentials: true to HttpClient other api calls
this.http.get('http://localhost:8000/path/to/get/resource',
{ withCredentials: true }).subscribe(response => {
Set-Cookies:
In the example in the Question, both client and server are in the same domain, localhost.
On deployment, this may not be the case.
Let us assume the domains as below,
Client : client1.client.com
Server: server1.server.com
A http request from the Angular web app in client1.client.com to https://server1.server.com/api/v1/getSomething has Set-Cookie: JSESSIONID=xyz in the response header.
The cookie will be set on server1.server.com and NOT on client1.client.com.
You can enter server1.server.com in the URL bar and see the cookie being set.
withCredentials:
There is no need for the angular app to read the cookie and send it in the following requests. withCredentials property of http request can be used for this.
Refer: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
Example:
public getSomething(): Observable<object> {
const httpOptions = {
withCredentials: true
};
return this.http.get(`${this.serverUrl}/getSomething`, httpOptions);
}
Refer: https://angular.io/api/common/http/HttpRequest
withCredentials will set the cookies from the server's domain in the requests to the server.
As mentioned before Set-Cookie: JSESSIONID=xyz in the response from server1.server.com will be set in server1.server.com. The Angular app in client1.client.com need not read it. withCredentials will take care of it.
cross domain issues:
When the server and client are in different domains, using withCredentials may not work in all browsers, as they are considered as third party cookies.
In my recent testing on May 2020, I found that withCredentials is not working in certain browsers when the client and server are in different domains.
In Safari, the issue occurs when "Prevent cross-site tracking" is enabled (by default). The issue is prevented by disabling the same. https://support.apple.com/en-in/guide/safari/sfri40732/mac
In Android apps, the issue can be avoided by using Chrome Custom Tabs instead of Android WebView. https://github.com/NewtonJoshua/custom-tabs-client , https://developer.chrome.com/multidevice/android/customtabs
Same domain:
Looks like mainstream browsers are moving to block third-party cookies.
Safari - Full Third-Party Cookie Blocking and More
Chrome (by 2022) - Building a more private web: A path towards making third party cookies obsolete
The solution is to have both the client and server in the same domain.
Client: client1.myapp.com
Server: server1.myapp.com
And in the Set-Cookie response include the root domain too.
Example: "JSESSIONID=xyz; Domain=.myapp.com; Path=/"
This will make sure the cookies are set in all cases.

Set-Cookie from a server to an XHR client in a different domain, setting the domain to the client's domain, should it work?

tl;dr, an XHR client in domain A is sending a request to a server in domain B, server responds with a Set-Cookie with Domain=A (the client's domain, the XHR's Origin), all CORS headers set correctly, should it work?
It's well known that one can't set a cookie to another domain. ( How to set a cookie for another domain
However given the following scenario:
Actors:
Client in domain A, a web based client
Server in domain B, setup with CORS headers permitting A as origin, including Access-Control-Allow-Credentials set to true
Communication flow 1 (baseline):
Client is issuing a simple GET request to the Server
Server responds with a cookie, and sets the Domain property to be of the server (Domain=B)
Client is sending another HXR request and has withCredentials=true
The cookie is sent back to the server without any issues
Note: the cookie sent in step #1 is not showing in document.cookies, even if it was not set as httpOnly (since it doesn't
belong to the client's domain). Also attempts to get it from the xhr
via looking at the "Set-Cookie" header, you'll be blocked, by design:
https://fetch.spec.whatwg.org/#forbidden-response-header-name it will
even won't show in Chrome dev tools under the network tab! but it will
still be sent)
Communication flow 2 (my question):
Client is issuing a simple GET request to the Server
Server responds with a cookie, but sets the Domain property to be of the client (Domain=A)
Client is sending an HXR request and has withCredentials=true
The cookie is not sent back and doesn't seem to be stored anywhere
Why am I a bit surprised? Since the XHR origin is A and it requests something that sets the cookie to domain A (if I look in Postman I clearly see the Set-Cookie header being sent with Domain being the same as the request's Origin), and I have the most permissive CORS setting for that, what's the reasoning behind not letting me do it? (I was expecting it to fail, but still made me wonder)
Questions
Where is the best place in the spec/RFC that it clarifies that this won't work also for XHR where the cookie Domain equals the Origin
What is the attack vector in scenario 2 if theoretically the browser did allow the server to store the cookie if and only if the Origin is the same as the cookie Domain and the CORS origin allows that Origin.
Is there another way to make it work? Maybe it works but my POC was setup incorrectly?
Appendix: Reasoning
I'm looking for a way to have a cross origin CSRF using something like the Cookie to header token method, but due to the cross origin issue, it seems that it's impossible. The only workaround I thought of is sending the CSRF token as a header from the server, then the client can just save it as a cookie it can access later, is there any other way to do it? Is this considered secure?
A resource can only set cookies for its host's registrable domain. If Facebook were to use Google Fonts, and Google could use that to override Facebook cookies, that'd be pretty disastrous.
As for where this is defined, step 5 and 6 of https://www.rfc-editor.org/rfc/rfc6265#section-5.3 handle this. (Fetch largely defers to this RFC when it comes to interpreting the Set-Cookie header on responses.)

Redirecting from HTTP to HTTPS w/ Simple Auth

I was hoping to get some recommendations on how to approach redirecting users from HTTP to HTTPS using an ember initializer with ember-simple-auth.
`import ENV from 'cio/config/environment'`
SSLInitializer =
name: 'ssl'
before: 'simple-auth-cookie-store'
initialize: (container, application) ->
application.deferReadiness()
# Redirect if hitting HTTP and SSL is enabled
if ENV.SSL and window.location.protocol is "http:"
window.location.href = "https:" + window.location.href.substring(window.location.protocol.length)
return false
application.advanceReadiness()
`export default SSLInitializer`
But it seems that the cookie gets invalidated even when the if statement evaluates to true. I've tried several things, including:
before: 'simple-auth'
before: 'store'
application.destroy() within the if statement, before the window.location.href is set
From what I can tell, after debugging. The app does redirect to HTTPS, but then the cookieName is not found in document.cookie. (https://github.com/simplabs/ember-simple-auth/blob/master/packages/ember-simple-auth-cookie-store/lib/simple-auth-cookie-store/stores/cookie.js#L154)
Before this method worked because we had simple snippet in the index.html, but w/ CSP we'd like to keep it in an initializer. Any recommendations?
Thanks!
You really should be forcing a redirect from HTTP to HTTPS from the server as doing it from the client does not add any real security.
Think about it, the user has downloaded the application to their browser from an insecure endpoint and from then on nothing can be trusted. Even a server based redirect is problematic since it relies on the redirect advice from an untrusted endpoint. Users should really be accessing things from an initial trusted starting point otherwise all bets are off. This is known as the secure referral problem and will likely never be solved because of the business model behind SSL certificates.
You also shouldn't really trust cookies from the untrusted HTTP domain in the trusted HTTPS domain unless you have a way to authenticate those cookies on the client. Sharing of cookies between HTTP/HTTPS is covered in RFC 2109 (Section 4.2.2 Set-Cookie Syntax).
This means:
A cookie set with "Secure" will be available only on HTTPS
A cookie set without "Secure" will be available on either HTTP or HTTPS.

Domain Wide Cookies

I have a.example.com and it sets a cookie for .example.com. Later on a.example.com sends an ajax request to b.example.com. I expect the.example.com cookie to be sent to b.example.com. But it is not.
I made sure the cookie was set on the right domain, but it does not seem to send the cookie in the ajax request for some reason.
# server A
res.cookie('images', tokens.images, { expires: config.pageExpiration(), secure: secure, domain: domain })
# server B
req.cookies.image
This makes sense, as you wouldn't expect *.com cookies to be mixed.
I suggest you pass your cookies through a server request if you really need to.