Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a devise cookie not set on XHR request?

I am using devise on a Rails4 app that serves a JSON API. I patched devise, so it accepts an authentication token via HTTP header instead of an URL parameter. That works pretty well.

A JS single page application based on that API works well. I can authenticate a user, and request this users resources inside the app.

In addition I'd like the server (devise) to set a cookie, so that cookie based auth works, and I can request a users private resource through non XHR requests as well. This does not work and I do not know why.

Im my devise initializer I have:

config.http_authenticatable_on_xhr = true

In my session_store initializer I set:

MyApp::Application.config.session_store :cookie_store, key: '_myapp_session', domain: :all, httponly: false, secure: false

When I run my single page app inside Chrome and inspect the network requests, I can see that every response to a request of a server side resource includes a Set-Cookie Header like so:

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Ua-Compatible: chrome=1
Access-Control-Allow-Origin: * //dont give me shelter
Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS, DELETE
Access-Control-Max-Age: 1728000
X-Meta-Request-Version: 0.2.8
Etag: "7b64cd327b9ff8dce6bb8b616aeee2b8"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 1d6be45e-ce45-40fd-b03b-358644826955
X-Runtime: 0.283514
Server: WEBrick/1.3.1 (Ruby/2.0.0/2013-06-27)
Date: Fri, 23 Aug 2013 10:36:04 GMT
Connection: Keep-Alive
Set-Cookie: _myapp_session=NndJcUd5QUJmRktkSkdTVTk1NTF3UHVKaW85QkVmTmpqZEM4Q3BqUW5ORzNyNG5HWmlnSWc5Yy83Nk16c3Q0dk1iVXQ0Q2JqTE1qZWZoaDgxNW1RQnErOHhsVG9rdEQ4cU1CNGsyNWsrVlk0OXlLRGo5c1BiN3NkdFlRdWJHVXBDamI1U1BrdlQ3Mmw3OWNZVWJkWGI1UWZqNDJ1VldxL0xvYkkwYVd5aHBYaU5sOElkZ3NSRXZVdGxlWHQxY1FteFh1OGU3NHowYU0xYTRLK2xuTEN4KzhzR2pjR25YWlZVSTZtZDkvUnZXbz0tLXJKMXNlV1gvcHFuaG5jU3YvNUJhSnc9PQ%3D%3D--5ecf40e2a678b467b77aa0c56494be8e079641d2; domain=.myapp.dev; path=/

My app is served from app.myapp.dev on port 9000 and requests the API at api.myapp.dev on port 3000. That works fine due to proper CORS config.

If I have a look inside Chrome's Resource panel under Cookies no cookie is set. Since there is no cookie, every non-XHR request to api.myapp.net does not authenticate a user.

I do not understand, why the cookie set header seems fine, but the cookie is not set?

Can anyone enlighten me?

Regards Felix

like image 275
GeorgieF Avatar asked Aug 23 '13 10:08

GeorgieF


People also ask

Why is cookie not being set?

Check out the OPTIONS response header ACCESS-CONTROL-ALLOW-CREDENTIAL whether it is set to true . If the server doesn't allow credentials being sent along, the browser will just not attach cookies and authorization headers. So this could be another reason why the cookies are missing in the POST cross-site request.

How do you set a CORS cookie?

Enabling Cookie in CORS needs the below configuration in the application/server. Set Access-Control-Allow-Credentials header to true. Access-Control-Allow-Origin and Access-Control-Allow-Headers should not be a wildcard(*). Cookie sameSite attribute should be None.


1 Answers

Ok, as I found out so far, it is possible to deliver a JS app from a.domain.com and let it fetch JSON data from b.domain.com with CORS enabled, but cookies sent by b.domain.com will not be set. Some kind of evil could go on here.

So the only way to solve the problem in my case is: Let the user login to the JS app, redirect him to an endpoint at b.domain.com and add an authentication token. The server at b logges the user in, can now set a cookie and redirects the user to the JS app afterwards.

That's a horrible solution, cause the app loads twice then, but as long as you deliver your app from another subdomain than your API there seems to be no better solution. Quite open for any.

Update

Setting cross domain cookies is indeed possible in case you are in control of the servers headers.

What solved the problem in my case was:

b.domain.com sets an additional response header Access-Control-Allow-Credentials to true. In addition inside backbone sync needs to be patched, so that the passed options object gets a key xhrFields with withCredentials:true. And that was it. When b.domain.com responds with a Set-Cookie header, the cookie is set, even if the JS app was delivered from a.domain.com. Not to forget: When you set Access-Control-Allow-Credentials, you can not use a wildcard for Access-Control-Allow-Origin (which you should not anyway).

like image 146
GeorgieF Avatar answered Sep 28 '22 04:09

GeorgieF