1. Introduction
This section is non-normative.
User Agents sometimes prevent content inside certain iframes from accessing data stored in client-side storage mechanisms like cookies. This can break embedded content which relies on having access to client-side storage.
The Storage Access API enables content inside iframes to request and be granted access to their client-side storage, so that embedded content which relies on having access to client-side storage can work in such User Agents. [STORAGE-ACCESS-INTRO]
2. Infrastructure
This specification depends on the Infra standard. [INFRA]
3. The Storage Access API
This specification defines a method to query whether or not a Document currently has access to its unpartitioned data (hasStorageAccess()), and a method that can be used to request access to its unpartitioned data (requestStorageAccess()).
Alex visits https://social.example/. The page sets a cookie. This cookie has been set in a first-party-site context.
Later on, Alex visits https://video.example/, which has an iframe on it which loads https://social.example/heart-button. In this case, the social.example Document doc is in a third party context, and the cookie set previously might or might not be visible from doc.cookie, depending on User Agent storage access policies.
Script in the iframe can call doc.hasStorageAccess() to determine if it has access to the cookie. If it does not have access, it can request access by calling doc.requestStorageAccess().
Unpartitioned data is client-side storage that would be available to a site were it loaded in a first-party-site context.
A Document is in a first-party-site context if it is the active document of a top-level browsing context. Otherwise, it is in a first-party-site context if it is an active document and the origin and top-level origin of its relevant settings object are same site with one another.
A Document is in a third party context if it is not in a first-party-site context.
3.1. User Agent state related to storage access
A storage access map is a map whose keys are partitioned storage keys and whose values are storage access flag sets.
User Agents maintain a single global storage access map.
What is the lifecycle of the global storage access map? How long do we remember its contents? Firefox and Safari differ here. [Issue #privacycg/storage-access#2]
When do we age out entries in the global storage access map? See also Scope of Storage Access. [Issue #privacycg/storage-access#5]
Each agent cluster has a storage access map.
When an agent cluster is created, its storage access map is initialized with a clone of the global storage access map.
To obtain the storage access map for a Document doc, run the following steps:
-
Return the storage access map of doc’s relevant agent's agent cluster.
A partitioned storage key is a tuple consisting of a top-level site (a site) and an embedded origin (an origin).
(("https", "news.example"), ("https", "social.example", null, null)) is a partitioned storage key whose top-level site is ("https", "news.example") and whose embedded origin is ("https", "social.example", null, null).
To generate a partitioned storage key for a Document doc, run the following steps:
-
Let settings be doc’s relevant settings object.
-
Let site be the result of obtaining a site from settings’ origin.
-
If doc’s browsing context is a top-level browsing context, return the partitioned storage key (site, site).
-
Let top-level site be the result of obtaining a site from settings’ top-level origin.
-
Return the partitioned storage key (top-level site, site).
A storage access flag set is a set of zero or more of the following flags, which are used to gate access to client-side storage for embedded origin when loaded in a third party context on top-level site:
- The has storage access flag
-
When set, this flag indicates embedded origin has access to its unpartitioned data when it’s loaded in a third party context on top-level site.
To obtain a storage access flag set for a partitioned storage key key from a storage access map map, run the following steps:
-
If map[key] does not exist, run these steps:
-
Let flags be a new storage access flag set.
-
Set map[key] to flags.
-
-
Return map[key].
3.2. Changes to Document
partial interface Document {Promise <boolean >hasStorageAccess ();Promise <undefined >requestStorageAccess (); };
When invoked on Document doc, the hasStorageAccess() method must run these steps:
-
Let p be a new promise.
-
If doc is not fully active, then reject p with an "
InvalidStateError"DOMExceptionand return p. -
If doc’s origin is an opaque origin, resolve p with false and return p.
-
Let global be doc’s relevant global object.
-
If global is not a secure context, then resolve p with false and return p.
-
If doc’s browsing context is a top-level browsing context, resolve p with true and return p.
-
If the top-level origin of doc’s relevant settings object is an opaque origin, resolve p with false and return p.
-
If doc’s origin is same origin with the top-level origin of doc’s relevant settings object, resolve p with true and return p.
-
Let key be the result of generating a partitioned storage key from doc.
-
If key is failure, resolve p with false and return p.
-
Let hasAccess be the result of running determine if a site has storage access with key and doc.
-
Queue a global task on the permissions task source given global to resolve p with hasAccess.
-
Return p.
Shouldn’t step 8 be same site?
When invoked on Document doc, the requestStorageAccess() method must run these steps:
-
Let p be a new promise.
-
If doc is not fully active, then reject p with an "
InvalidStateError"DOMExceptionand return p. -
Let global be doc’s relevant global object.
-
If global is not a secure context, then reject p with a "
NotAllowedError"DOMExceptionand return p. -
If this algorithm was invoked when doc’s
Windowobject did not have transient activation, reject p with a "NotAllowedError"DOMExceptionand return p. -
If doc’s browsing context is a top-level browsing context, resolve and return p.
-
If doc is not allowed to use the
"storage-access"permission, reject p with a "NotAllowedError"DOMExceptionand return p. -
If doc’s origin is an opaque origin, reject p with a "
NotAllowedError"DOMExceptionand return p. -
If the top-level origin of doc’s relevant settings object is an opaque origin, reject p with a "
NotAllowedError"DOMExceptionand return p. -
If doc’s origin is same origin with the top-level origin of doc’s relevant settings object, resolve and return p.
-
If doc’s active sandboxing flag set has its sandbox storage access by user activation flag set, reject p with a "
NotAllowedError"DOMExceptionand return p. -
Let key be the result of generating a partitioned storage key from doc.
-
If key is failure, reject p with a "
NotAllowedError"DOMExceptionand return p. -
Let map be the result of obtaining the storage access map for doc.
-
Let flag set be the result of obtaining the storage access flag set with key from map.
-
If flag set’s has storage access flag is set, resolve and return p.
-
Otherwise, run these steps in parallel:
-
Let hasAccess be a new promise.
-
Determine the storage access policy with key, doc and hasAccess.
-
Queue a global task on the permissions task source given global to
-
Set flag set’s has storage access flag.
-
If hasAccess is true, resolve p.
-
Reject p with a "
NotAllowedError"DOMException.
-
-
-
Return p.
We shouldn’t use the permissions task source here. [Issue #privacycg/storage-access#144]
Shouldn’t step 9 be same site?
3.2.1. User Agent storage access policies
Different User Agents have different policies around whether or not sites may access their unpartitioned data when they’re in a third party context. User Agents check and/or modify these policies when client-side storage is accessed (see § 3.4 Changes to various client-side storage mechanisms) as well as when hasStorageAccess() and requestStorageAccess() are called.
To determine if a site has storage access with partitioned storage key key and Document doc, run these steps:
-
Let map be the result of obtaining the storage access map for doc.
-
Let flag set be the result of obtaining the storage access flag set with key from map.
-
If flag set’s has storage access flag is set, return true.
-
Return false.
To determine the storage access policy for partitioned storage key key with Document doc and Promise p, run these steps:
-
Let map be the result of obtaining the storage access map for doc.
-
Let flag set be the result of obtaining the storage access flag set with key from map.
-
Let implicitly granted and implicitly denied (each a boolean) be the result of running an implementation-defined set of steps to determine if key’s embedded origin's request for storage access on key’s top-level site should be granted or denied without prompting the user.
-
Let global be doc’s relevant global object.
-
If implicitly granted is true, queue a global task on the permissions task source given global to resolve p, and return.
-
If implicitly denied is true, queue a global task on the permissions task source given global to reject p with a "
NotAllowedError"DOMException, and return. -
Let permissionState be the result of requesting permission to use "
storage-access". -
If permissionState is "granted", queue a global task on the permissions task source given global to resolve p, and return.
-
Unset flag set’s has storage access flag.
-
If doc’s
Windowobject has transient activation, consume user activation with it. -
Queue a global task on the permissions task source given global to reject p with a "
NotAllowedError"DOMException.
3.3. Changes to navigation
Before changing the current entry of a session history, run the following steps:
-
Let doc be current entry’s
Document. -
Let map be the result of obtaining the storage access map for doc’s browsing context's top-level browsing context.
-
Let key be the result of generating a partitioned storage key from doc.
-
If key is failure, abort these steps.
-
Let flag set be the result of obtaining the storage access flag set with key from map.
-
Unset flag set’s has storage access flag.
What this section should look like ultimately hinges on [Issue #privacycg/storage-access#3]
Add links to current entry and session history to reflect the navigation and session history rewrite. [Issue #privacycg/storage-access#137]
3.4. Changes to various client-side storage mechanisms
This API only impacts HTTP cookies. A future revision of this API might impact other client-side state. [RFC6265]
3.4.1. Cookies
This API is intended to be used with environments and user agent configurations that block access to unpartitioned cookies in a third party context. At the time of this writing, this concept has not yet been integrated into the HTTP-network-or-cache fetch and cookie algorithms. To allow for such an integration, the cookie store will need to be modified to receive information about the top-level and embedded site of the request (to determine whether to attach cross-site, partitioned, or no cookies) as well as whether the request was made for a document that has storage access, through running the determine if a site has storage access algorithm that is defined in this specification.
Once the cookie store allows for receiving information about storage access, we would update HTTP-network-or-cache fetch and cookie to run determine if a site has storage access and pass this information to the cookie store when retrieving cookies.
When getting unpartitioned cookies from the cookie store with storage access, user agents will still follow applicable SameSite restrictions (i.e., not attach cookies marked SameSite=Strict or SameSite=Lax in third party contexts).
Note: User agents could apply different default values for the SameSite cookie attribute. This could lead to unpartitioned cookies without a SameSite attribute being attached to requests in some user agents (where SameSite=None is the default), but not in others (where SameSite=Lax is the default). Web developers are encouraged to set the SameSite attribute on their cookies to not run into issues.
3.5. Sandboxing storage access
A sandboxing flag set has a sandbox storage access by user activation flag. This flag prevents content from requesting storage access.
To the parse a sandboxing directive algorithm, add the following under step 3:
- The sandbox storage access by user activation flag, unless tokens contains the
allow-storage-access-by-user-activationkeyword.
4. Permissions Integration
The Storage Access API defines a powerful feature identified by the name "storage-access". It defines the following permission-related algorithms:
- permission query algorithm
-
To query the "
storage-access" permission, given aPermissionDescriptorpermissionDesc and aPermissionStatusstatus:-
Set status’s
stateto permissionDesc’s permission state. -
If status’s
stateis denied, set status’sstateto prompt.Note: The "denied" permission state is not revealed to avoid exposing the user’s decision to developers. This is done to prevent retaliation against the user and repeated prompting to the detriment of the user experience.
-
- permission key type
-
A permission key of the "
storage-access" feature is a tuple consisting of a site top-level and an origin requester.Note that this will likely change to a (site, site) keying. [Issue #privacycg/storage-access#147]
- permission key generation algorithm
-
To generate a new permission key for the "
storage-access" feature, given an environment settings object settings, run the following steps:-
Let topLevelSite be settings’ top-level site.
-
Let embeddedOrigin be settings’ origin.
-
Return (topLevelSite, embeddedOrigin).
-
- permission key comparison algorithm
-
To compare the permission keys key1 and key2 for the "
storage-access" feature, run the following steps:
5. Permissions Policy Integration
The Storage Access API defines a policy-controlled feature identified by the string "storage-access". Its default allowlist is "*".
Note: A Document’s permissions policy determines whether any content in that document is allowed to request storage access using requestStorageAccess(). If disabled in any document, calling requestStorageAccess() in that document will reject.
6. Privacy considerations
The Storage Access API enables the removal of cross-site cookies. Specifically, it allows the authenticated embeds use case to continue to work. As such, the API provides a way for developers to re-gain access to cross-site cookies, albeit under further constraints.
A nested Document gains access to the same cookies it has as the active document of a top-level browsing context when it calls requestStorageAccess() and is returned a resolving Promise. With these cookies it can authenticate itself to the server and load user-specific information.
While this functionality comes with a risk of abuse by third parties for tracking purposes, it is an explicit goal of the API and a key to its design to not undermine the gains of cross-site cookie deprecation. Importantly, we do not degrade privacy properties when compared to pre-removal of cross-site cookies. This follows from a lack of platform-specific information used in the spec to prevent stateless tracking and the only state added being a permission scoped to the sites of the embedding and embedded Document.
Our privacy considerations are more challenging where default cross-site cookies are already deprecated. The challenge is to decide when and how to permit the Storage Access API to be used to revert a cookie-less (or cookie-partitioned) nested Document to a pre-deprecation state, giving it access to its unpartitioned data.
In an ideal case, a nested Document would only be able to gain access to its unpartitioned data if:
-
the user interacts with the nested
Document -
the nested
Documentis permitted by the embedder to use the API -
the nested
Documentis a secure context -
the user grants express, pairwise permission to the embeddee to use its cookies in the embedder
-
the user is not inundated by requests for the "
storage-access" permission so their express permission is not undermined by fatigue
This specification requires the first three of implementers. This provides guarantees that the user is aware of the content of the nested Document, the embedder has not opted out of the nested Document's authentication, and the cross-site cookies are not disclosed to network attackers, respectively.
The last two points are in tension. In an ideal world, we would show a prompt to the user in every call to requestStorageAccess(). But, this would allow pages to prompt the user so frequently as to put the last point at the discretion of the page– a state we find unacceptable. User agents should prevent over-prompting of the user.
document.requestStorageAccess().Thus, the last two points represent a key point of compromise. We permit implementation-defined behavior on when to grant or deny requests for unpartitioned data without requiring user choice so long as they meet all other requirements. This compromise weakens the privacy guarantees of this proposal, specifically point 4, to a degree under the control of the implementer and gives the implementer the power to render it impossible to get storage access with this API. However, this has proven necessary to enable condition 5 to be possible given our implementers' differing stances on the compromise between these two points.
Developer experience suffers where user agents differ greatly in their implementation-defined behavior, and therefore user agents should aim to minimize or standardize silent grants and denies.
6.1. Permission scope
Another tension in the design of the API is what to use to key the "storage-access" permission: origins or sites. We chose sites because we believe them to be acceptable boundaries for privacy while enabling existing uses of same site and cross-origin nested Documents on the same page with only one user prompt.
7. Security considerations
It is important that this spec not degrade security properties of the web platform, even when compared to post-removal of cross-site cookies. Third-party cookie removal has potential benefits for security, specifically in mitigating attacks that rely upon authenticated requests, e.g. CSRF. We do not wish the Storage Access API to be a foothold for such attacks to leverage.
To this end, we limit the impact of a "storage-access" permission grant to only give access to unpartitioned data to the nested Document that called requestStorageAccess() and only until the nested Document navigates across an origin boundary. This ensures that only |origins with a page that call requestStorageAccess() will be making credentialed requests, and moreover the embedee page can control which embedder it permits via the Content Security Policy "frame-ancestors" directive. This retains an origin-scoped control for security purposes by the embedee.
7.1. Reputational attacks
This also is effective at preventing another attack: one on the embedee’s reputation. We consider any cross-site authenticated request to have potential reputational harm as consumers become more privacy conscious. Therefore a first-party or sibling cross-site causing an embedded resource to be requested with the user’s authentication cookies would constitute an attack on the reputation of that cross-site’s owner. This is also a reason we require this API to be used in a secure context: so a network adversary cannot induce an embedee to use this API.
The embedder has control over which nested Documents have the ability to become authenticated, or even display a permission request to the user via the Permission Policy and nested Document sandboxing.
7.2. Notification abuse
Notification abuse was also considered while specifying the Storage Access API. Specifically, we require user interaction in the nested Document and consume that rejection on a denial to restrict the conditions a permission prompt will be shown to the user. This mitigates attacks such as re-requesting a permission immediately after the user denies it.
8. Automation
For the purposes of user-agent automation and application testing, this document defines the following extension command for the [WebDriver] specification.
8.1. Set Storage Access
| HTTP Method | URI Template |
|---|---|
| POST | /session/{session id}/storageaccess |
The Set Storage Access extension command modifies the storage access policy for the current browsing context.
The remote end steps are:
-
Let blocked be the result of getting a property from parameters named
blocked. -
If blocked is not a boolean return a WebDriver error with WebDriver error code invalid argument.
-
Let embedded origin be the result of getting a property from parameters named
origin. -
If embedded origin is not a single U+002A ASTERISK character (*), then:
-
Let parsedURL be the the result of running the URL parser on embedded origin.
-
If parsedURL is failure, then return a WebDriver error with WebDriver error code invalid argument.
-
Set embedded origin to parsedURL’s origin.
-
-
If the current browsing context is not a top-level browsing context return a WebDriver error with WebDriver error code unsupported operation.
-
Let doc be the current browsing context's active document.
-
Let settings be doc’s relevant settings object.
-
Let top-level site be the result of obtaining a site from settings’s origin.
-
If embedded origin is a single U+002A ASTERISK character (*), then:
-
If blocked is
true, then:-
Run an implementation-defined set of steps to ensure that no site has access to its unpartitioned data when loaded in a third party context on top-level site.
-
-
Otherwise, if blocked is
false, then:-
Run an implementation-defined set of steps to ensure that any site has access to its unpartitioned data when loaded in a third party context on top-level site.
-
-
-
Otherwise:
-
If embedded origin is same site with top-level site return a WebDriver error with WebDriver error code unsupported operation.
-
If blocked is
true, then:-
Run an implementation-defined set of steps to ensure that embedded origin does not have access to its unpartitioned data when loaded in a third party context on top-level site.
-
-
Otherwise, if blocked is
false, then:-
Run an implementation-defined set of steps to ensure that embedded origin has access to its unpartitioned data when loaded in a third party context on top-level site.
-
-
-
If the above implementation-defined step of steps resulted in failure, return a WebDriver error with WebDriver error code unknown error.
-
Return success with data
null.
Acknowledgements
This specification builds on the foundations created by former editors John Wilander, who invented the Storage Access API, and Theresa O’Connor, who wrote significant portions of the initial text. We are grateful for their ideas and contributions.
Many thanks to Anne van Kesteren, Ben Kelly, Brad Girardeau, Brad Hill, Brady Eidson, Brandon Maslen, Chris Mills, Dave Longley, Domenic Denicola, Ehsan Akhgari, Geoffrey Garen, Jack Frankland, James Coleman, James Hartig, Jeffrey Yasskin, Kushal Dave, Luís Rudge, Maciej Stachowiak, Matias Woloski, Mike O’Neill, Mike West, Pete Snyder, Rob Stone, Stefan Leyhane, Steven Englehardt, Travis Leithead, Yan Zhu, Zach Edwards, and everyone who commented on whatwg/html#3338, privacycg/proposals#2, and privacycg/storage-access/issues for their feedback on this proposal.
Thanks to the WebKit Open Source Project for allowing us to use the Storage Access API Prompt image, which was originally published on webkit.org.