Bug 208049

Summary: Javascript can't access a SameSite=Strict cookie after page is loaded after a redirect from a third party site
Product: WebKit Reporter: Aleksei <alekseipetrov>
Component: Page LoadingAssignee: John Wilander <wilander>
Status: RESOLVED FIXED    
Severity: Normal CC: achristensen, bastien.caudan, beidson, cdumez, ericlaw, ews-watchlist, japhet, katherine_cheney, konrad, kyle.bremner, laurens.von.assel, mjs, peleg3, unknown9595, webkit-bug-importer, wilander
Priority: P2 Keywords: InRadar
Version: Safari 13   
Hardware: Unspecified   
OS: Unspecified   
See Also: https://bugs.webkit.org/show_bug.cgi?id=219650
Attachments:
Description Flags
Cookies sent does not match document.cookies
none
Redirect chain in Chrome
none
Redirect chain in Safari
none
Fetch request with cookies
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch for landing none

Aleksei
Reported 2020-02-21 04:48:58 PST
Steps to reproduce: Users visit https://some-integration.com GET https://some-integration.com HTTP/1.1 Because they're not logged in, user-agent redirects to https://accounts.spotify.com/authorize (standard OAuth 2.0 flow). HTTP/1.1 302 Found Location: https://accounts.spotify.com/authorize?client_id=<client_id>&redirect_uri=https%3A%2F%2Fsome-integration.com%2Fsuccess https://accounts.spotify.com/authorize returns the html form (for user to approve scopes) and sets csrf_token cookie. GET https://accounts.spotify.com/login?continue=https://*.spotify.net HTTP/1.1 HTTP/1.1 200 OK Content-Type: text/html;charset=UTF-8 Set-Cookie: csrf_token=<omitted>; Domain=.accounts.spotify.com; Path=/; Secure; SameSite=Strict ... Actual results: JS call document.cookie doesn't contain csrf_token cookie. Expected results: JS call document.cookie contains csrf_token=<omitted>
Attachments
Cookies sent does not match document.cookies (425.81 KB, image/png)
2020-05-01 06:27 PDT, Robert
no flags
Redirect chain in Chrome (249.78 KB, image/png)
2020-05-01 10:59 PDT, Robert
no flags
Redirect chain in Safari (450.94 KB, image/png)
2020-05-01 11:00 PDT, Robert
no flags
Fetch request with cookies (656.21 KB, image/png)
2020-05-01 11:02 PDT, Robert
no flags
Patch (27.27 KB, patch)
2021-05-13 18:12 PDT, John Wilander
no flags
Patch (27.27 KB, patch)
2021-05-13 18:15 PDT, John Wilander
no flags
Patch (20.88 KB, patch)
2021-05-14 14:39 PDT, John Wilander
no flags
Patch (23.74 KB, patch)
2021-05-14 15:38 PDT, John Wilander
no flags
Patch for landing (23.66 KB, patch)
2021-05-14 21:53 PDT, John Wilander
no flags
Radar WebKit Bug Importer
Comment 1 2020-02-22 16:53:59 PST
John Wilander
Comment 2 2020-02-22 18:19:45 PST
Thanks for filing! Some screening questions: 1. Is this for Safari? WebKit supports multiple HTTP backends where cookies are implemented. 2. If for Safari, which OS and which version? 3. Has this worked before, i.e. is it a regression? 4. Do you see different behavior in other browsers? 5. I see both spotify.com and spotify.net in the HTTP metadata you provide. Is the .net domain ever loaded or just there for things that might happen after the CSRF token is read through document.cookie?
Aleksei
Comment 3 2020-02-23 01:12:25 PST
1. Yes. 2. Safari 13.0.5, MacOS 10.14.6 3. I don't know, didn't use Strict cookies before. 4. Yes, Google Chrome behaves as expected. Latest Firefox though also has this bug. 5. My bad. It's a typo. Line `GET https://accounts.spotify.com/login?continue=https://*.spotify.net HTTP/1.1` should be replaced with `GET https://accounts.spotify.com/authorize?client_id=<client_id>&redirect_uri=https%3A%2F%2Fsome-integration.com%2Fsuccess HTTP/1.1` So, spotify.net domain isn't importatnt in this case. Just a redirect from https://some-integration.com to https://accounts.spotify.com matters.
Aleksei
Comment 4 2020-02-23 03:51:39 PST
I've added a simple node app with poc https://github.com/anstick/strict-cookie-issue-poc
John Wilander
Comment 5 2020-02-23 08:18:56 PST
(In reply to Aleksei from comment #3) > 1. Yes. > 2. Safari 13.0.5, MacOS 10.14.6 > 3. I don't know, didn't use Strict cookies before. > 4. Yes, Google Chrome behaves as expected. Latest Firefox though also has > this bug. When you say “as expected,” do you mean by spec? I was a long while since I read the particulars of SameSite cookies. Two engines doing it one way and one doing it another may otherwise imply the opposite of what you’re saying. > 5. My bad. It's a typo. Line `GET > https://accounts.spotify.com/login?continue=https://*.spotify.net HTTP/1.1` > should be replaced with `GET > https://accounts.spotify.com/ > authorize?client_id=<client_id>&redirect_uri=https%3A%2F%2Fsome-integration. > com%2Fsuccess HTTP/1.1` > > So, spotify.net domain isn't importatnt in this case. Just a redirect from > https://some-integration.com to https://accounts.spotify.com matters. Got it. Thanks!
Aleksei
Comment 6 2020-02-24 01:11:44 PST
(In reply to John Wilander from comment #5) > > 4. Yes, Google Chrome behaves as expected. Latest Firefox though also has > > this bug. > > When you say “as expected,” do you mean by spec? I was a long while since I > read the particulars of SameSite cookies. Two engines doing it one way and > one doing it another may otherwise imply the opposite of what you’re saying. AFAIK the spec (https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-05) doesn't explicitly set the behavior of Document interface in browsers. The RFC a) defines the difference between "same-site" and "cross-site" requests and the algorithm to distinguish between them b) defines whether a Cookie header should be sent along with requests or not based on "SameSite" attribute. I dunno about any spec regarding JS access to cookies based on `SameSite` attribute (plz point them out if you know). However, if we look at the sequence of requests: 1. Request: GET https://some-integration.com HTTP/1.1 2. Response: HTTP/1.1 302 Found Location: https://accounts.spotify.com/authorize?client_id=<client_id>&redirect_uri=https%3A%2F%2Fsome-integration.com%2Fsuccess Request: GET https://accounts.spotify.com/authorize?client_id=<client_id>&redirect_uri=https%3A%2F%2Fsome-integration.com%2Fsuccess HTTP/1.1 3. Response: HTTP/1.1 200 OK https://accounts.spotify.com/authorize?client_id=<client_id>&redirect_uri=https%3A%2F%2Fsome-integration.com%2Fsuccess HTTP/1.1 ... (for example) Request: POST https://accounts.spotify.com/authorize and apply the algorithm from the RFC, the 1->2 will be a "cross-domain" request (I don't expect any Strict cookies to be sent to the backend with this request), however 2->3 is a "same-site" so there should not be any limitations on cookies (I don't consider using `iframe` in my statement). The current Safari behaviour makes it impossible to use `SameSite=Strict` cookies for us, because you never know how this page gonna be opened (via redirect or not). And I don't think it's only for us: any internet page using SPA and Double Submit Cookie CSRF pattern (https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf) for form submission can't have `csrf` cookie being strict (or can't use strict cookies at all).
Robert
Comment 7 2020-05-01 06:27:31 PDT
Created attachment 398178 [details] Cookies sent does not match document.cookies
Robert
Comment 8 2020-05-01 06:54:22 PDT
Comment on attachment 398178 [details] Cookies sent does not match document.cookies I'm having the same issue The redirect sets the cookies via Set-Cookie Fetch requests are sending correct cookie headers But document.cookie on both sub-domains is an empty string Storage tab is also empty
Robert
Comment 9 2020-05-01 10:59:51 PDT
Created attachment 398203 [details] Redirect chain in Chrome
Robert
Comment 10 2020-05-01 11:00:20 PDT
Created attachment 398204 [details] Redirect chain in Safari
Robert
Comment 11 2020-05-01 11:02:28 PDT
Created attachment 398205 [details] Fetch request with cookies So the redirect has finished and the cookies should be set and accessable through document.cookie, but it doesn't return the cookies I'm looking for both on abc & xzy.example.com But when I do a fetch request, the cookies which "haven't" been set are passed along with the request.
Robert
Comment 12 2020-05-01 11:06:54 PDT
Comment on attachment 398205 [details] Fetch request with cookies -
Aleksei
Comment 13 2020-08-20 04:10:59 PDT
Any updates on this issue?
Peleg Rosenthal
Comment 14 2020-12-08 10:01:22 PST
We're seeing the same issue in Safari 14.01: same-site cookies set after a redirect from a third-party site aren't accessible via `document.cookie`. Similarly to OP, we rely on double-submit CSRF cookies. We noticed the issue when Sendgrid's click-tracking redirects to our site and our cookies are no longer accessible to JS. A few other things we've noticed: 1. Issue persists after refreshing the page. For example: Sendgrid redirects to first-party (ie xyz.com); cookie inaccessible to JS; refresh xyz.com; `Set-Cookie` header is in the response; cookie is included in subsequent xhr requests, yet is still inaccessible to JS. 2. Pre-existing cookies that were accessible to JS, after a redirect, are no longer accessible to JS. For example: cookie accessible to JS w/in xyz.com, but when user lands on xyz.com after a redirect, the same cookie is no longer accessible to JS. 3. In addition to not being accessible via `document.cookie`, the aforementioned cookies are also not shown in the "Storage" tab in the web inspector. In the meantime we've mitigated this issue by reducing same-site level to "Lax," which works given we don't use CSRF protection for GETs, but it'd be ideal if Strict cookies could be used in the same way. This was all tested on Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15
Eric Lawrence (MSFT)
Comment 15 2021-04-29 11:01:55 PDT
I think this is the same as https://bugs.chromium.org/p/chromium/issues/detail?id=925311#c26 The spec has changed and Firefox and Chrome both allow |document.cookie| access to cookies set with SameSite=Strict. Safari 14.2 Release 123 does not. Repro: https://debugtheweb.com/test/cookie/samesite/ Simply set the cookies on both of the first two domains, then flip between them using the links.
John Wilander
Comment 16 2021-05-06 16:51:31 PDT
Thanks all! I'm looking at this.
John Wilander
Comment 17 2021-05-13 18:12:52 PDT
John Wilander
Comment 18 2021-05-13 18:15:35 PDT
John Wilander
Comment 19 2021-05-13 18:16:03 PDT
Fixed typo in change log.
Alex Christensen
Comment 20 2021-05-13 18:19:38 PDT
Comment on attachment 428589 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=428589&action=review > Source/WebCore/platform/network/SameSiteInfo.h:32 > +enum class CookieAccessForHTTP : bool { Yes, No }; I would reverse this and call it CookieAccessForJS. As it stands now, the use of CookieAccessForHTTP::Yes is more for the cache than HTTP, which is entirely done by CFNetwork. > Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm:259 > // FIXME: Seems like this newer code path can be used for watchOS and tvOS too. We should probably fix this soon. I would be quite surprised if CFNetwork were different in this way.
John Wilander
Comment 21 2021-05-13 18:22:51 PDT
(In reply to Alex Christensen from comment #20) > Comment on attachment 428589 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=428589&action=review > > > Source/WebCore/platform/network/SameSiteInfo.h:32 > > +enum class CookieAccessForHTTP : bool { Yes, No }; > > I would reverse this and call it CookieAccessForJS. As it stands now, the > use of CookieAccessForHTTP::Yes is more for the cache than HTTP, which is > entirely done by CFNetwork. I actually started out with it called CookieAccessForDOM which follows our code naming well. Then I switched to CookieAccessForHTTP to align with the spec language. See change log comment. I can go back to CookieAccessForDOM as long as we think that's good given how the spec frames it. > > Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm:259 > > // FIXME: Seems like this newer code path can be used for watchOS and tvOS too. > > We should probably fix this soon. I would be quite surprised if CFNetwork > were different in this way. Me too. I was surprised to see this comment.
Chris Dumez
Comment 22 2021-05-13 18:23:24 PDT
Comment on attachment 428590 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=428590&action=review r=me with changes. > Source/WebCore/platform/network/SameSiteInfo.h:32 > +enum class CookieAccessForHTTP : bool { Yes, No }; Please put No first so its value is 0. Since this is logically a boolean, I think it needs a prefix, like IsCookieAccessForHTTP. Honestly, if it were me, I would have gone the other way around and called this IsCookieAccessForDOM, since this is the case we're special-casing. Also, I think IsCookieAccessForHTTP is a bit confusing since WebKit doesn't really deal with HTTP cookies itself. We do use cookies for things like WebSocket (but then it's not really HTTP I think). > Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm:266 > + sameSiteInfoForGetCookies = SameSiteInfo { While lie about being same site instead of calling the one in the else case (which does not seem to care about same-site)? [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL partition:partition completionHandler:completionHandler];
John Wilander
Comment 23 2021-05-13 18:26:36 PDT
(In reply to Chris Dumez from comment #22) > Comment on attachment 428590 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=428590&action=review > > r=me with changes. > > > Source/WebCore/platform/network/SameSiteInfo.h:32 > > +enum class CookieAccessForHTTP : bool { Yes, No }; > > Please put No first so its value is 0. Will fix. > Since this is logically a boolean, I think it needs a prefix, like > IsCookieAccessForHTTP. I wanted to do that and looked at other boolean enums and couldn't find a clear pattern. Will fix though. Much better with Is prefix. > Honestly, if it were me, I would have gone the other way around and called > this IsCookieAccessForDOM, since this is the case we're special-casing. > Also, I think IsCookieAccessForHTTP is a bit confusing since WebKit doesn't > really deal with HTTP cookies itself. We do use cookies for things like > WebSocket (but then it's not really HTTP I think). We all agree it's better in WebKit code. I just went with HTTP because of the spec language. > > Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm:266 > > + sameSiteInfoForGetCookies = SameSiteInfo { > > While lie about being same site instead of calling the one in the else case > (which does not seem to care about same-site)? > [storage _getCookiesForURL:url mainDocumentURL:mainDocumentURL > partition:partition completionHandler:completionHandler]; Not sure what you're saying here. :)
John Wilander
Comment 24 2021-05-13 18:27:54 PDT
Will fix the unused parameter warning for tvOS and watchOS.
Chris Dumez
Comment 25 2021-05-13 18:47:37 PDT
Comment on attachment 428590 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=428590&action=review > Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm:265 > + if (accessForHTTP == CookieAccessForHTTP::No && sameSiteInfo && sameSiteInfo->isTopSite) { Seems wrong to overwrite the sameSiteInfo we're being given at super low-level here. Why can't we pass the right sameSiteInfo from the start? (Presumably in WebCookieJar::cookies() & CookieJar::cookies(), where the SameSiteInfo is constructed to retrieve DOM cookies).
John Wilander
Comment 26 2021-05-14 14:39:18 PDT
Chris Dumez
Comment 27 2021-05-14 14:50:28 PDT
Comment on attachment 428661 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=428661&action=review > Source/WebCore/loader/CookieJar.cpp:62 > +SameSiteInfo CookieJar::sameSiteInfo(const Document& document, IsCookieAccessForDOM isAccessForDOM) I would rename IsCookieAccessForDOM to IsForDOMCookieAccess. I think it makes for sense for a function called sameSiteInfo() which is really getting cookies. > Source/WebCore/loader/CookieJar.cpp:65 > + auto sameSiteInfo = SameSiteInfo::create(loader->request()); Should we consider passing the IsCookieAccessForDOM flag to SameSiteInfo::create() too? Then we wouldn't need to post-process the sameSiteInfo, we'd get the right one right away. Your call. > Source/WebCore/loader/CookieJar.cpp:98 > + result = session->cookiesForDOM(document.firstPartyForCookies(), sameSiteInfo(document, IsCookieAccessForDOM::Yes), url, frameID, pageID, includeSecureCookies, ShouldAskITP::Yes, shouldRelaxThirdPartyCookieBlocking(document)); Any reason we're not using IsCookieAccessForDOM::Yes in setCookiesFromDOM() ? Which the asymmetry?
John Wilander
Comment 28 2021-05-14 15:38:00 PDT
Chris Dumez
Comment 29 2021-05-14 15:41:43 PDT
Comment on attachment 428675 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=428675&action=review r=me with nit > Source/WebCore/loader/CookieJar.h:71 > + static SameSiteInfo sameSiteInfo(const Document&, IsForDOMCookieAccess isAccessForDOM = IsForDOMCookieAccess::No); Can simply be: `IsForDOMCookieAccess = IsForDOMCookieAccess::No`. No need for the parameter name.
John Wilander
Comment 30 2021-05-14 15:43:22 PDT
(In reply to Chris Dumez from comment #29) > Comment on attachment 428675 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=428675&action=review > > r=me with nit > > > Source/WebCore/loader/CookieJar.h:71 > > + static SameSiteInfo sameSiteInfo(const Document&, IsForDOMCookieAccess isAccessForDOM = IsForDOMCookieAccess::No); > > Can simply be: `IsForDOMCookieAccess = IsForDOMCookieAccess::No`. No need > for the parameter name. Thanks, Chris! Will fix and land if EWS is happy.
John Wilander
Comment 31 2021-05-14 21:48:27 PDT
mac-debug-wk1 test failure is unrelated.
John Wilander
Comment 32 2021-05-14 21:53:07 PDT
Created attachment 428714 [details] Patch for landing
EWS
Comment 33 2021-05-14 22:26:49 PDT
Committed r277534 (237762@main): <https://commits.webkit.org/237762@main> All reviewed patches have been landed. Closing bug and clearing flags on attachment 428714 [details].
Note You need to log in before you can comment on or make changes to this bug.