Android cookie behavior with cookies from multiple hosts sharing domain - android

I am seeing behavior on a native Android app where the last response that returns a Set-Cookie is not the same cookie used on successive calls to the same domain. The cookie store can return an older copy of that cookie.
The code uses the default CookieHandler and CookieManager with the default InMemoryCookieStore.
The app can receive session cookies from multiple hosts on the same domain, the session cookies are good on any calls to the same domain. The app calls about 4 different hosts, host1.example.com, through host4. Any of these calls can respond with a Set-Cookie header to refresh the SESSION cookie.
The problem I see is the Cookie store will store these cookies in a Map keyed by the full hostname. The domain on the cookie is: .example.com
host3.example.com - SESSION=xyx; path=/; domain=.example.com; secure; HTTPOnly
host2.example.com - SESSION=sde; path=/; domain=.example.com; secure; HTTPOnly
host1.example.com - SESSION=8xd; path=/; domain=.example.com; secure; HTTPOnly
host4.example.com - SESSION=22d; path=/; domain=.example.com; secure; HTTPOnly
When the cookies are requested for a new http request, the cookie store iterates over the map looking for the domain matching the domain on the cookie, which is .example.com. It finds this in the first entry, say host3, then continues on through the list. All other domains that match the cookie are excluded as duplicates and it returns the SESSION=xyx which can be old and expired (There is no expiration set on these cookies.) The last update on the Cookie may have been from host1 so the returned cookie from host3 is not the latest.
What's also odd, is the full host is ignored and only the domain is considered. The request url cam be host1.example.com but it'll still match host3 since matches the domain and is the first in the entry set from the map. I only find that as odd since the full hostname is used as the key but never considered.
I looked through the RCF6265 and didn't see any mention to this specific behavior but the rfc seemed to sound like it should provide the latest cookie from that domain.
Is there a defined behavior for this and is the Android InMemoryCookieStore behaving incorrectly?

Related

Why is a SameSite=Strict cookie for http://127.0.0.1:12345/ not being sent in Android browsers?

We have a cross-platform app that runs a web server on 127.0.0.1, on a randomised port. The server code is the same on each platform.
An example URL: http://127.0.0.1:12345/
To authenticate requests, a session cookie is set on the initial response. That cookie has HttpOnly and SameSite=Strict set, to improve security.
Just this week we have found that in current versions of most Android browsers (Chrome 109.0.5414.85, Chrome Beta 110.0.5481.40, Firefox 109.1.1, Edge 109.0.1518.53), authentication is failing. With the Chrome dev tools, I can see that the cookie is not being sent in requests, although it is in the cookie store. If I manually change SameSite=Strict to SameSite=Lax (undesirable) using the dev tools, the cookie is sent, and everything starts working normally.
Opera (73.1.3844.69816) is not affected, and neither was Chrome before it was upgraded on the test tablet (possibly a pre-100 version). I have not been able to find any relevant recent changes in Chrome.
The puzzling part is, this problem doesn't exist on Windows or iOS (Mac not tested yet); there is no problem using the app on Windows under the 109.x versions of Chrome, Firefox, and Edge.
If it was a browser policy issue (despite this being a same-origin request), I would expect it to carry across platforms. If it was a browser bug, I wouldn't expect both Chrome and Firefox to be affected.
What am I missing?
We still don't know the reason for the problem, but a work-around is to change the initial response (the one that sets the session cookie) from a 302 redirect to a 200 response with a <meta http-equiv="refresh"> tag (as described in this answer).
Other things that we tried, without success:
Use http://localhost or http://localhost.localdomain instead of http://127.0.0.1
Set the Secure attribute on the cookie
Set Location on the 302 response to an absolute URL (http://127.0.0.1:12345/) instead of just the path
Clearing browser cache and cookies
Set Cache-Control: private, no-store, must-revalidate on the 302 response
With the redirect, the Chrome dev tools showed this message about the session cookie:
This cookie was blocked because it had the "SameSite=Strict" attribute and the request was made from a different site. This includes top-level navigation requests initiated by other sites.
Some bugs and commits that may be relevant:
1221316 - SameSite context type should consider redirect chain - chromium
This is still behind a disabled-by-default feature flag: SameSite cookies: Hide cross-site redirect chain check behind a Feature. It can be enabled using the --enable-experimental-cookie-features command-line argument. Doing that on Windows made no difference to behaviour.
150066 - Set-cookie ignored for HTTP response with status 302 - chromium (closed wontfix)
696204 - Cookies are ignored on 302 redirects - chromium (closed wontfix)
This comment talks about setting the Cache-Control header on the redirect to prevent the browser caching the response. The rest of the discussion seems to be about an unreliably-reproducible problem.
Change cookie domain handling for IP address host

Requests not being made from android app, with max-age set

I have an api that returns an object as the response, and an etag and max age as headers. The response looks like this:
HEADERS:
'x-frame-options': 'SAMEORIGIN',
'x-xss-protection': '1; mode=block',
'x-content-type-options': 'nosniff',
'x-download-options': 'noopen',
'strict-transport-security': 'max-age=15778476000; includeSubDomains',
'access-control-allow-origin': '*',
etag: 'c69148a0489a95058e729bde7fd4bf32bf2077b1cba8d4fcf0c2da6e696fa33e',
'cache-control': 'private,max-age=43200'
BODY:
{
id: 1985,
url: "https://example.com",
...
}
The desired scenario is that an android application makes the request to ask for this data. The Api returns the data, along with an max age of 43200 secs.
If a request is made before 43200 secs pass, the application has the data from the last response cached. The application makes the request nevertheless, the back-end service compiles the response data, uses the request's etag to decide whether the response data has changed. If the data has changed, it returns a 200 http status and the data. Otherwise it returns a 403 status and no data.
The application receives the response. It uses fast networking to handle caching (says my android teammate). If a 200 status code was returned, the data are updated. Otherwise the application keeps the old data.
If a request is made after 43200 secs have passed, the application no longer has the cached response or it's etag. The request is made, the data are considered as 'new' even if nothing has changed in the data, the status code 200 is returned along with max-age header as above.
What actually happens:
For some reason, after the first request is processed and the application receives the data, no request is made until 43200 secs have passed. The android developer says they see that the request is made and 0 bytes are returned, but when I monitor the requests in the server I don't see any made towards this API.
This doesn't make sense, since max-age does not imply that no requests are made. It simply instructs the application to keep the data in the cache for the duration.
Am I missing the idea of how cache, etags and max-age work?
Back-end is built in node js, and uses express for routing.
You've only set the max-age and private directives in the Cache-Control header. The actual behaviour you have described is the correct behaviour since max-age directive has no bearing on forcing the cache to validate responses each time a request is made. For that, you have to add the no-cache directive as well to the Cache-Control header.
The no-cache directives tells the cache to always validate the stored response with the origin server before serving it (i.e., the desired behaviour you have described). Upon revalidation, the stored response will be valid for another 43200 secs (max-age). Without the no-cache directive, the HTTP client is free to make use of cached responses. Which I guess is why your friend said the request was made, but 0 bytes were returned (browsers also show 0 bytes for responses served from the cache). And which is also why you didn't observe any incoming requests to the server.
Have a look at this article from Google for a good overview on HTTP caching:
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching
If you need in-depth detail on how responses are constructed from caches, have a look at the RFC7234 spec: https://www.rfc-editor.org/rfc/rfc7234#section-4

Android. OkHttp cookies are not saved after relaunching the app

I use the following code to setup cookies
PersistentCookieJar cookieJar = new PersistentCookieJar(
new SetCookieCache(), new SharedPrefsCookiePersistor(context));
clientBuilder.cookieJar(cookieJar);
Retrofit retrofit = builder.client(
clientBuilder.build()).build();
This is the header I received from server:
Set-Cookie: wordpress_logged_in_6041590398a33ab947560d559f09d479=capad%7C1488467742%7CSttfNHrOkwd67CteCGepJyv3bNU2SeSW0URepOPxCe5%7C5091b3bbcc334e520cec85c4c9b8e26a07a962d3c00c8c709b87f90018370f60; path=/; secure; httponly
Your cookie (Set-Cookie: wordpress_logged_in_[etc]; path=/; secure; httponly) has no expiration set.
This makes it a "session" cookie, which should not be persisted. Your PersistentCookieJar likely follows the correct spec in not persisting any cookies without an expiration.
You can either manually add on an expiration when you receive this cookie (perhaps with an OkHttp interceptor), or create your own version of the PersistentCookieJar that persists all cookies (this is probably not a good idea).
Another option would be to respect the fact that whoever is sending that cookie wants it to be a session cookie.

Java Servlet, Cookies and Android

I am writing an Android app which submits a username and password to a Java Servlet hosted on Google App Engine. I am writing both the Android app. and Servlet.
The username and password are packaged into a POST request on the device and the servlet doPost() method checks the values. If the username and password are correct I request a session...creating it if it doesn't exist:
HttpSession session = request.getSession(true);
In this session I store a name value pair "logged" and "true".
Back on the android device a cookie is returned along with an HTTP status of 200 OK. This all seems fine, since the session on the server is implemented using cookies (transparent to me since I'm just using the session API).
All subsequent HTTP POSTs made by the android device package up the cookie into the HTTP POST so that it can request .jsp pages or use other servlets which inspect the session for the "logged and "true" value (i.e. protected pages).
The problem: A cookie is returned even if the following code is NOT run:
HttpSession session = request.getSession(true);
i.e. the username and password were false. This isn't such a security issue since the "logged" and "true" name value pair is never set so the application cannot use the .jsp or other servlets. However, I was using the fact that a cookie had been returned from the POST request to the device as a sign that authentication was successful.
Why am I getting a cookie even though I don't use or request the use of a session?
My current solution is to create an additional cookie in the servlet and check for this cookie on the device. HOWEVER, this cookie is not the one packaged into subsequent POSTS from the device since it is not the cookie associated with the session containing the "logged" "true" value. This seems hacky. Clearly I ave misunderstood something.
Most (or perhaps all) servlet containers will always assign a user a session cookie if they do not already have one, whether the webapp being served explicitly requests a session or not. Google App Engine is no different in this regard. You shouldn't assume anything based upon the mere existence or non-existence of a cookie other than that the device has made a request to the server and received some response back.
If you want to verify that the login was successful on the device, why not just send back a response to the login request that it can easily parse. For instance, a simple JSON-snippet like {"status": "success"} would do, or even just the literal text string "success".
Your second cookie approach definitely sounds a bit hacky. Presumably your authentication request is already sending some response back to the device (it has to be, if cookies are being sent). What do you feel that you gain from using a cookie that you don't get by just sending some status message back as part of the response?

Coupling Android App with Web Service

I am creating a Webapplication - Food Review site for which we are also developing an android application.
I want to know how to couple the application session of a particular user over the Internet with the application.
When the user logs in thru the application how can i serve them content related to the user alone. How can i know which user is requesting.
I am from a PHP background so i am a little confused how its done in the mobile application
You can maintain state in the same way a web application does. When a browser stores a cookie all that means is that requests sent to a matching domain get the cookie in the HTTP header. When you construct your HTTP request in Android you have complete control of the headers so it's easy to add your cookie value.
E.G. Post to your authenticator, sending client details. The authenticator would normally respond including the cookie in its response headers, here's a sanitised version of google's cookie setting response headers:
Cache-Control:private, max-age=0
Content-Encoding:gzip
Content-Length:173
Content-Type:text/html; charset=UTF-8
Date:Fri, 04 Mar 2011 12:24:32 GMT
Expires:Fri, 04 Mar 2011 12:24:32 GMT
Location:http://www.google.co.uk/
Server:GSE
Set-Cookie:SID=UNIQUESIDGOESHERE;Domain=.google.co.uk;Path=/;Expires=Mon, 01-Mar-2021 12:24:32 GMT
HSID=SOMETHINGELSEUNIQUEHERE;Domain=.google.co.uk;Path=/;Expires=Mon, 01-Mar-2021 12:24:32 GMT;HttpOnly
You can read that value and attach it to any http requests you make and the server would be non the wiser that you aren't a normal web browser.
Android is pretty much stateless. (I mean even if it can use states, I haven't come across any such example so far)
So I don't think you can have a session as you have on the web.
You can have a token method to authenticate users for a particular time period. like the OAuth implemented web services do.
Your android app sends the token for each request and your server manages the generation and life of that token along with the permissions.
How does this sound?

Categories

Resources