The Storage Access API

Draft Community Group Report,

This version:
https://privacycg.github.io/storage-access/
Issue Tracking:
GitHub
Inline In Spec
Editors:
(Mozilla)
(Google)
(Apple Inc.)
Former Editors:
(Apple Inc.)
(Apple Inc.)

Abstract

The Storage Access API enables content in iframes to request access to website data (such as cookies).

Status of this document

This specification is intended to be merged into the HTML Living Standard. It is neither a WHATWG Living Standard nor is it on the standards track at W3C.

It was published by the Privacy Community Group. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

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.

To determine whether the user agent explicitly allows unpartitioned cookie access, given a tuple tuple consisting of two sites, run the following steps. This algorithm returns "none", "allow" or "disallow".

Note: A user agent’s settings might explicitly allow or disallow unpartitioned cookie access through per-site allow-lists, the user changing global browser settings, or similar custom overrides.

  1. If the user agent does not have explicit settings for unpartitioned cookie access for tuple, return "none".

  2. If the user agent’s settings explicitly allow unpartitioned cookie access for tuple, return "allow".

  3. Assert: the user agent’s settings explicitly disallow unpartitioned cookie access for tuple.

  4. Return "disallow".

3.1. Changes to user agent state related to storage access

Modify the definition of environment in the following manner:

  1. Add a new member called has storage access of type boolean.

Modify the definition of source snapshot params in the following manner:

  1. Add a new member called has storage access of type boolean.

  2. Add a new member called environment id of type opaque string.

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:

  1. Let p be a new promise.

  2. If doc is not fully active, then reject p with an "InvalidStateError" DOMException and return p.

  3. If doc’s origin is an opaque origin, resolve p with false and return p.

  4. Let global be doc’s relevant global object.

  5. If global is not a secure context, then resolve p with false and return p.

  6. If the top-level origin of doc’s relevant settings object is an opaque origin, resolve p with false and return p.

  7. Let browsingContext be doc’s browsing context.

  8. Let topLevelSite be the result of obtaining a site from the top-level origin of doc’s relevant settings object.

  9. Let embeddedSite be the result of obtaining a site from doc’s origin.

  10. Run the following steps in parallel:

    1. Let explicitSetting be the result of determining whether the user agent explicitly allows unpartitioned cookie access with (topLevelSite, embeddedSite).

    2. Let permissionState be the result of getting the current permission state given "storage-access" and global.

    3. Queue a global task on the networking task source given global to:

      1. If explicitSetting is "disallow", resolve p with false.

      2. If explicitSetting is "allow", resolve p with true.

      3. Assert: explicitSetting is "none".

      4. If browsingContext is a top-level browsing context, resolve p with true.

      5. If browsingContext is same authority with browsingContext’s top-level browsing context's active document, resolve p with true.

        "same authority" here is a placeholder for a future concept that allows user agents to perform same site checks while adhering to additional security aspects such as the presence of a cross-site parent document, see whatwg/storage#142. In practice, this might involve comparing the site for cookies or performing a same site check with the top-level document.

      6. If permissionState is granted, resolve p with global’s has storage access.

        Note: The global storage access permission state takes precedence over the local has storage access flag here, in order to immediately reflect a possible user choice to revoke the permission in their settings.

      7. Resolve p with false.

  11. Return p.

When invoked on Document doc, the requestStorageAccess() method must run these steps:

  1. Let p be a new promise.

  2. If doc is not fully active, then reject p with an "InvalidStateError" DOMException and return p.

  3. Let global be doc’s relevant global object.

  4. Let settings be doc’s relevant settings object.

  5. If global is not a secure context, then reject p with a "NotAllowedError" DOMException and return p.

  6. If doc is not allowed to use "storage-access", reject p with a "NotAllowedError" DOMException and return p.

  7. If doc’s origin is an opaque origin, reject p with a "NotAllowedError" DOMException and return p.

  8. If settings’s top-level origin is an opaque origin, reject p with a "NotAllowedError" DOMException and return p.

  9. If doc’s active sandboxing flag set has its sandbox storage access by user activation flag set, reject p with a "NotAllowedError" DOMException and return p.

  10. Let browsingContext be doc’s browsing context.

  11. Let topLevelSite be the result of obtaining a site from the top-level origin of doc’s relevant settings object.

  12. Let embeddedSite be the result of obtaining a site from doc’s origin.

  13. Let has transient activation be whether doc’s Window object has transient activation.

  14. Run the following steps in parallel:

    1. Let process permission state be an algorithm that, given a permission state state, runs the following steps:

      1. Queue a global task on the networking task source given global to:

        1. If state is granted:

          1. Set global’s has storage access to true.

          2. Resolve p with undefined.

        2. Else:

          1. Consume user activation given global.

          2. Reject p with a "NotAllowedError" DOMException.

    2. Let explicitSetting be the result of determining whether the user agent explicitly allows unpartitioned cookie access with (topLevelSite, embeddedSite).

    3. If explicitSetting is "disallow":

      1. Run process permission state with denied.

      2. Abort these steps.

    4. If explicitSetting is "allow":

      1. Run process permission state with granted.

      2. Abort these steps.

    5. Assert: explicitSetting is "none".

    6. If browsingContext is a top-level browsing context:

      1. Run process permission state with granted.

      2. Abort these steps.

    7. If embeddedSite is same site with topLevelSite:

      NOTE: This check is same site on purpose, to allow embedded sites to use requestStorageAccess() to opt into storage access without involvement from the end user in scenarios where storage access is restricted for security and not privacy purposes.

      1. Run process permission state with granted.

      2. Abort these steps.

    8. Let previous permission state be the result of getting the current permission state given "storage-access" and global.

    9. If previous permission state is not prompt:

      1. Run process permission state with previous permission state.

      2. Abort these steps.

    10. If has transient activation is false:

      1. Run process permission state with denied.

      2. Abort these steps.

    11. Let permissionState be the result of requesting permission to use "storage-access".

      NOTE: Note that when requesting permissions and deciding whether to show a prompt, user agents apply implementation-defined behavior to shape the end user experience. Particularly for storage-access, user agents are known to apply custom rules that will grant or deny a permission without showing a prompt.

    12. Run process permission state with permissionState.

  15. Return p.

NOTE: The intent of this algorithm is to always require user activation before a storage-access permission will be set. Though it is within the means of user agents to set storage-access permissions based on custom heuristics without prior user activation, this specification strongly discourages such behavior, as it could lead to interoperability issues.

When snapshotting source snapshot params:

  1. Set has storage access to sourceDocument’s has storage access.

  2. Set environment id to sourceDocument’s relevant settings object's id.

To the create navigation params by fetching algorithm, insert the following step as step 3:

  1. Let originalURL be entry’s URL.

When creating request’s reserved client in create navigation params by fetching:

  1. Set reserved client's has storage access to sourceSnapshotParams’s has storage access if all of the following hold:

    1. sourceSnapshotParams’s environment id equals navigable’s active document's relevant settings object's id.

    2. originalURL’s origin is same origin with currentURL’s origin.

    3. response is null or response’s has-cross-origin-redirects is false.

  2. Otherwise, set request’s reserved client's has storage access to false.

When setting up a window environment settings object:

  1. Set settings object’s has storage access to reserved environment’s has storage access.

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 accessing the environment's has storage access 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 pass the environment's has storage access 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:

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 a PermissionDescriptor permissionDesc and a PermissionStatus status:
  1. Set status’s state to permissionDesc’s permission state.

  2. If status’s state is denied, set status’s state to 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 a site requester.

(("https", "news.example"), ("https", "social.example")) is a permission key for "storage-access" whose top-level is ("https", "news.example") and whose requester is ("https", "social.example").

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:
  1. Let topLevelSite be the result of obtaining a site from settingstop-level origin.

  2. Let embeddedSite be the result of obtaining a site from settingsorigin.

  3. Return (topLevelSite, embeddedSite).

permission key comparison algorithm
To compare the permission keys key1 and key2 for the "storage-access" feature, run the following steps:
  1. If key1’s top-level is not same site with key2’s top-level, return false.

  2. If key1’s requester is not same site with key2’s requester, return false.

  3. Return true.

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:

  1. the user interacts with the nested Document

  2. the nested Document is permitted by the embedder to use the API

  3. the nested Document is a secure context

  4. the user grants express, pairwise permission to the embeddee to use its cookies in the embedder

  5. 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.

A modal dialog box which states 'Do you want to allow “video.example” to use cookies and website data while browsing “news.example”? This will allow “video.example” to track your activity.' and which has two buttons, “Don’t Allow” and “Allow”.
An example prompt which could be shown to the user when a site calls 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:

  1. Let blocked be the result of getting a property from parameters named blocked.

  2. If blocked is not a boolean return a WebDriver error with WebDriver error code invalid argument.

  3. Let embedded origin be the result of getting a property from parameters named origin.

  4. If embedded origin is not a single U+002A ASTERISK character (*), then:

    1. Let parsedURL be the the result of running the URL parser on embedded origin.

    2. If parsedURL is failure, then return a WebDriver error with WebDriver error code invalid argument.

    3. Set embedded origin to parsedURL’s origin.

  5. If the current browsing context is not a top-level browsing context return a WebDriver error with WebDriver error code unsupported operation.

  6. Let doc be the current browsing context's active document.

  7. Let settings be doc’s relevant settings object.

  8. Let top-level site be the result of obtaining a site from settings’s origin.

  9. If embedded origin is a single U+002A ASTERISK character (*), then:

    1. If blocked is true, then:

      1. 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.

    2. Otherwise, if blocked is false, then:

      1. 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.

  10. Otherwise:

    1. If embedded origin is same site with top-level site return a WebDriver error with WebDriver error code unsupported operation.

    2. If blocked is true, then:

      1. 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.

    3. Otherwise, if blocked is false, then:

      1. 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.

  11. If the above implementation-defined step of steps resulted in failure, return a WebDriver error with WebDriver error code unknown error.

  12. 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.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[RFC6265]
A. Barth. HTTP State Management Mechanism. April 2011. Proposed Standard. URL: https://httpwg.org/specs/rfc6265.html
[RFC6265BIS]
Cookies: HTTP State Management Mechanism. Editor's Draft. URL: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WebDriver]
Simon Stewart; David Burns. WebDriver. URL: https://w3c.github.io/webdriver/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[STORAGE-ACCESS-INTRO]
John Wilander. Introducing Storage Access API. February 2018. Blog post. URL: https://webkit.org/blog/8124/introducing-storage-access-api/

IDL Index

partial interface Document {
  Promise<boolean> hasStorageAccess();
  Promise<undefined> requestStorageAccess();
};

Issues Index

"same authority" here is a placeholder for a future concept that allows user agents to perform same site checks while adhering to additional security aspects such as the presence of a cross-site parent document, see whatwg/storage#142. In practice, this might involve comparing the site for cookies or performing a same site check with the top-level document.