- Matt Reichhoff (mreichhoff@chromium.org)
- Kaustubha Govind (kaustubhag@chromium.org)
- Johann Hofmann (johannhof@chromium.org)
- Issues on this repo are welcome!
- Feedback on the issue in the storage access repo is also welcome!
Enabled-by-default cross-site cookie access is in the process of being deprecated by several major browsers. Multiple substitutes have been proposed, like the Storage Access API and the SameParty cookie attribute in the First-Party Sets proposal.
With the proposal to abandon active development of the SameParty cookie attribute in favor of requiring a call to the Storage Access API to enable cross-site cookie use cases, this document briefly discusses how the existing Storage Access API works and how First-Party Set membership can be applied as part of it.
While the integration of Storage Access API with First-Party Sets alone will solve some use cases, many legacy issues with 3rd party cookie usage involve instances where user interaction within an <iframe> is difficult to retrofit, e.g. because of the use of images or script tags requiring cookies.
Both Firefox and Safari have run into these issues before and solved them through the application of an internal-only “requestStorageAccessForOrigin” API(1,2), which is applied on a case-by-case basis by custom browser scripts (Safari: 3,4 Firefox: 5,6,7). While such an internal API should not be treated as setting precedent, it could be evidence that such an API would be useful for developers.
Accordingly, this document proposes a variant of this API as an extension to the Storage Access API to improve its ergonomics, with a requirement of increased trust between browser and sites as outlined in the Security & Privacy Considerations section.
- Enable use cases for which
SamePartycookies were proposed, but in a manner that better aligns with existing browser APIs. - Within the bounds of guardrails like First-Party Sets, enable greater flexibility and easier adoption of the Storage Access API.
- Allow user agents to define storage access policies that are informed by site author-specified information.
- Maintaining parity with the existing
SamePartycookie proposal, particularly in characteristics like synchronicity, is not a goal. - Enforcement of a specific browser treatment or behavior for StorageAccessAPI requests (either the existing
requestStorageAccessor the proposedrequestStorageAccessForOrigin) as part of the implementation defined steps is not a goal. Every user agent should still be free to take custom steps, including prompting users or other heuristics.
The existing Storage Access API specifies requestStorageAccess, but delegates the decision on whether to grant access to the browser. Firefox and Safari have each implemented their own set of requirements, such as whether the user has previously interacted with the requester in a top-level context; the number of existing grants for the origin in the session; whether the user consents to the sharing; and others.
While a heuristics-based approach is possible, browsers could also choose to use First-Party Sets membership information to inform their behavior. For example, browsers with FPS integration may choose to automatically grant or deny requests based on First-Party Set membership. In a hypothetical scenario, if fps-member2.example, embedded in an <iframe> on fps-member1.example, calls requestStorageAccess, and the two domains are in the same First-Party Set, the call could resolve successfully and access could be granted.
The same call made by other-party.example, also embedded in an iframe on fps-member1.example, where other-party.example is not in the same First-Party Set, could be rejected and cookie use denied.
For those browsers that prompt the user, FPS membership could also potentially inform such behavior (for example, as an anti-abuse measure).
This is compatible with the requestStorageAccess specification. The user activation and other requirements would remain in place; this behavior is simply the browser logic step (step 9 of the spec), and other browsers would continue with prompting or other measures that match the product expectations of their user base.
<!--top-level site: fps-member1.example; fps-member2.example is in the same First-Party Set-->
<html>
<body>
<iframe src="https://fps-member2.example">
<script>
document.addEventListener('click', function() {
document.requestStorageAccess().then(/*auto-granted; same-party*/);
});
</script>
</iframe>
<iframe src="https://other-party.example">
<script>
document.addEventListener('click', function() {
document.requestStorageAccess().catch(/*auto-rejected; not same-party*/);
});
</script>
</iframe>
</body>
</html>
Since the requestStorageAccess API was originally designed for authenticated embeds, it has requirements that are perhaps uniquely well-suited for that category of use-cases. Specifically, it is only possible for the embedded party to request access, and only from within <iframe> elements that have received user interaction. However, these restrictions place adoption costs on websites that have functionality deployed across multiple sites, where cross-site subresources may include images or JavaScript files instead of <iframe>-embedded documents. A similar discussion previously resulted in the existing requestStorageAccess API operating at the page level, rather than frame-only.
This document proposes a similar API, document.requestStorageAccessForOrigin, which would allow the embedding site to request access it knows it needs on behalf of origins it embeds.
This new API would be very similar to the existing requestStorageAccess. It would still require activation, though of the top-level document; would still delegate to per-browser logic, so that each browser can customize the experience to their user' expectations; and would functionally be equivalent to if requestStorageAccess had been called by the passed-in domain with the same top-level context.
This API could be treated as similar in principle to browser-specific compatibility measures, implemented in Safari and Firefox, where an internal API is invoked, based on site-based allowlists, that requests cross-site cookie access on behalf of embedded origins.
Like with the proposed requestStorageAccess implementation described in the previous section of this document, granting access could be determined by First-Party Set membership.
Requiring user interaction in <iframe> elements helps the original requestStorageAccess API deter spam and abuse from first-parties and their embedded third-party scripts. To compensate for lack of this protection, browsers should require additional trust signals to grant storage access via the proposed API, such as FPS membership. This is discussed in Privacy & Security Considerations.
This is the standard use of the requestStorageAccess API; usage would be equivalent to that implemented in other browsers. The browser-specific logic, as described above, would simply be a check on First-Party Set membership.
See the numerous examples available elsewhere for sample code; this proposal does not modify developer-facing use of the existing requestStorageAccess API.
In contrast with the existing requestStorageAccess API, the proposed extension would allow non-iframe use, and would afford more control to the top-level party over what access is requested when.
With grant logic based only on First-Party Set membership, example use could be:
<!--
Top-level site: fps-member1.example. fps-member2.example is in the same First-Party Set. other-party.example is not.
Note that all <script> tags below would be new post-3rd party cookie deprecation.
Assume fps-member2.example has previously set two cookies:
Set-Cookie: sameSiteLax=123; SameSite=Lax
Set-Cookie: sameSiteNone=456; SameSite=None; Secure
-->
<html>
<head>
<script>
document.requestStorageAccessForOrigin('https://fps-member2.example')
.then(
/*not called;no activation.*/)
.catch(/*called due to top-level document lacking activation at load time*/);
</script>
</head>
<body>
<button id='play-button'></button>
<script>
const playButton = document.getElementById('play-button');
playButton.addEventListener('click', function(){
document.requestStorageAccessForOrigin('https://fps-member2.example')
.then(
/*
called;has activation, same First-Party Set is sufficient.
Cookie `sameSiteNone=456` available. Cookie `sameSiteLax=123` is not.
Image tags or other assets could be requested:
*/
let img = document.createElement('img');
img.src='https://fps-member2.example/profile_pic.png';
document.body.appendChild(img);
)
.catch(/*not called due to lacking activation*/);
document.requestStorageAccessForOrigin('https://other-party.example')
.then(/*for v1, rejected; not in the same First-Party Set*/)
.catch(/*called due to not being in the same First-Party Set*/);
});
</script>
<!--initial page load: would not have cookies; see event handler above-->
<img src='https://fps-member2.example/profile_pic.png'>
<!--
Would have access to its cross-site cookies, after the button is clicked and the process runs as expected. Future page-loads would remember this for some TBD period of time.
-->
<iframe src='https://fps-member2.example'></iframe>
<!--
Would require a separate call to requestStorageAccessForOrigin, because of origin, not site, scoping.
-->
<iframe src='https://sub-domain.fps-member2.example'></iframe>
</body>
</html>
The proposed spec could include a set of steps for the browser to follow, much like is done with requestStorageAccess. The spec could include a function that takes a string as the origin:
function requestStorageAccessForOrigin(origin)
Where a draft set of steps could be:
- If the browser is not processing a user gesture, reject.
- If the document already has been granted access, resolve.
- If the document has a null origin, or if the requested domain is invalid or has a null origin, reject.
- If the document's frame is not the main frame, reject.
- If the requested origin is equal to the main frame's, resolve.
- Check any additional rules that the browser has. Reject if some rule is not fulfilled.
- If the browser implements First-Party Sets, this could entail a check equivalent to
IsSameParty(top_level_site, requested_origin) - Other browsers could continue with their own rules, e.g., requiring prior first party interaction with the requested origin, prompting the user, etc.
- If the browser implements First-Party Sets, this could entail a check equivalent to
- Grant future subresource requests access to cookies and store that fact for the purposes of future calls to requestStorageAccessForOrigin()
One could imagine either a singular API:
// follows the existing 1x1 pattern established by requestStorageAccess
// returns a Promise similarly
requestStorageAccessForOrigin("origin.example")
Or a plural one:
// allows, for example, use of Promise.all()
requestStorageAccessForOrigins(["origin1.example","origin2.example"])
Given the increased complexity with potential user prompts, and the 1x1 nature of the existing requestStorageAccess API, the singular version is recommended. Note that it is also simpler to switch from singular to plural than from plural to singular, should that ever become necessary.
The existing requestStorageAccess API is scoped to site for the top-level page in both Safari and Firefox. The embeddee, however, is scoped to site in Safari and origin in Firefox.
This has been the subject of debate. This proposal is to scope the grant similarly to Firefox, with a key like: {top-level site, requested origin}. This does mean repeated calls for origins like www.site.example and site.example.
A previous version of this proposal suggested embedded site scoping. See a recent security analysis for information about the benefits of embedded origin scoping.
As discussed in the introduction, both Firefox and Safari have implemented an internal-only “requestStorageAccessForOrigin” API(1,2), that is applied on a case-by-case basis by custom browser scripts (Safari: 3,4 Firefox: 5,6,7).
This approach is not preferred, as it favors websites that have access to the corresponding browser's developers, and may not produce equitable outcomes. In addition, it does not allow site authors to proactively fix issues without interacting with browser developers.
Forward declaration of storage access requirements remains under discussion. This proposal is not intended to replace (or otherwise take a stance on) that option, which may still be relevant in the future. Instead, this proposal attempts to resolve adoption considerations that aren’t directly resolved by the forward declaration-based design, which requires a top-level navigation to the embedded origin and is intended to address identity/login use-cases.
Using the Storage Access API introduces a dependency on JavaScript for a site wanting to use cookies within a First-Party Set context. While this may be a fairly small selection of sites, the sites and their clients may not have previously required JavaScript, which increases the effort for adoption.
In a similar way to using the allow attribute on an iframe to enable specific features for a domain map to an equivalent Permissions Policy, it would be possible to provide an equivalent for a storage access call.
For example, the JavaScript call:
document.requestStorageAccessForOrigin('https://fps-member2.example')
Could be equivalent to an HTTP header, possibly using permissions policy syntax:
Permissions-Policy: storage-access=(self "https://fps-member2.example")
While this option may be attractive in the future, and would be doable in a First-Party Set membership-driven approval system, it is outside the scope of this document, which builds on the existing JavaScript API. Such an option is instead considered a potential future work item.
By exposing a new access-granting API, especially one that relaxes the current <iframe> requirement of requestStorageAccess and allows for arbitrary domains to be passed in, care must be taken not to open additional security issues or abuse vectors. It is easy to imagine an untrusted top-level domain requesting access on behalf of an unrelated site. Such access could enable CSRF, clickjacking, or other attacks.
To prevent unrelated sites from requesting access, browsers should seek additional trust signals to enable the API to resolve successfully. First-Party Sets is one such mechanism; it could ensure that a relationship exists between the caller and the passed-in site. However, sites designated as part of the "service" subset should not be allowed to gain access on behalf of other sites in the set. Browsers that don’t support First-Party Sets could utilize other mechanisms, like user prompts, allowlists, denylists, or other heuristics.
Existing cross-site protections, like the SameSite cookie attribute, will continue to be respected; access granted by requestStorageAccessForOrigin would apply only to SameSite=None cookies. By ensuring a default SameSite setting of at least Lax, browsers can ensure that the embedded resources opted into cross-domain sharing by setting SameSite to None.
However, this protection (alongside x-frame-options and others) may not be sufficient, since sites may globally set SameSite=None cookies that are required only on a subset of resources that are intended to be consumed across site boundaries. Additional explicit opt-in, perhaps by ensuring that cookies are not sent except on CORS-enabled endpoints, or by ensuring that only certain domains in a First-Party Set are authorized to successfully call requestStorageAccessForOrigin, may be desirable.
A side effect of disabling SameSite=None cookies is that attacks like CSRF become significantly harder to carry out. While the existing requestStorageAccess API already allows a mechanism to opt out of this protection (especially due to the fact that the API unlocks cross-site cookies for all requests to subresources on the requesting site, not just the specific document making the request), requestStorageAccessForOrigin could be used more broadly due to its relaxation of the <iframe> requirement. Additionally, requestStorageAccessForOrigin is invoked by the embedder, as opposed to the embedded origin which gets access to cross-site cookies. This may make additional opt-in requirements for embedded resources, like those described above, more attractive.
There is also a risk of abuse of the API by top-level documents, for example by attempting to get user consent to data sharing with an unrelated third-party, or by using prior interaction requirements to infer browsing history. This is especially true because of the ubiquity of third-party scripts included in top-level contexts. Gating access on First-Party Sets (which also guarantee mutual exclusivity, preventing a single domain from being present in multiple sets) is one mechanism by which this concern can be mitigated. Additional anti-abuse mechanisms, especially for those user agents that do not support First-Party Sets, could include:
- Consuming (rather than merely requiring) user activation on the top-level website, which would prevent repeated attempts at gaining access without additional user activity.
- Limiting the number of calls to the API on a given page load.
- Monitoring usage in order to quiet permission prompts or auto-reject requests by disruptive sources.
- Injecting timing noise into resolution of promises to prevent side channel attacks (for example: an adversary using the time to resolution to infer that the user had been prompted, and that the other browser requirements, like prior interaction with the domain at the top level, had been fulfilled).
- TBD
Many thanks for valuable feedback and advice from:
- Artur Janc
- The existing Storage Access API spec, the MDN Storage Access API documentation and the Safari documentation were all instrumental in authoring this document.