├── .gitignore ├── images └── storage-access-prompt.png ├── .pr-preview.json ├── w3c.json ├── .github └── workflows │ └── deploy.yml ├── PULL_REQUEST_TEMPLATE.md ├── Makefile ├── explainers ├── fedcm-saa-privacy-questionnaire.md └── storage-access-for-fedcm.md ├── tag-security-questionnaire.md ├── README.md └── storage-access.bs /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /images/storage-access-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacycg/storage-access/HEAD/images/storage-access-prompt.png -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "storage-access.bs", 3 | "type": "bikeshed", 4 | "params": { 5 | "force": 1 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": 120428, 3 | "contacts": ["hober", "TanviHacks", "erik-anderson", "wseltzer", "weiler"], 4 | "repo-type": "cg-report" 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-python@v4 13 | with: 14 | python-version: '3.10' 15 | - run: pip install bikeshed 16 | - run: bikeshed update 17 | - run: make 18 | - uses: peaceiris/actions-gh-pages@v3 19 | with: 20 | github_token: ${{ secrets.GITHUB_TOKEN }} 21 | publish_dir: build 22 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | - [ ] At least two implementers are interested (and none opposed): 6 | * … 7 | * … 8 | - [ ] [Tests](https://github.com/web-platform-tests/wpt) are written and can be reviewed and commented upon at: 9 | * … 10 | - [ ] [Implementation bugs](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md#handling-pull-requests) are filed: 11 | * Chromium: … 12 | * Gecko: … 13 | * WebKit: … 14 | 15 | (See [WHATWG Working Mode: Changes](https://whatwg.org/working-mode#changes) for more details.) 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile assumes you have a local install of bikeshed. Like any 2 | # other Python tool, you install it with pip: 3 | # 4 | # python3 -m pip install bikeshed && bikeshed update 5 | 6 | # It also assumes you have doctoc installed. This is a tool that 7 | # automatically generates Table of Contents for Markdown files. It can 8 | # be installed like any other NPM module: 9 | # 10 | # npm install -g doctoc 11 | 12 | .PHONY: all publish clean update-explainer-toc 13 | .SUFFIXES: .bs .html 14 | 15 | publish: build/index.html build/images/storage-access-prompt.png 16 | 17 | all: publish update-explainer-toc 18 | 19 | clean: 20 | rm -rf build *~ 21 | 22 | update-explainer-toc: README.md Makefile 23 | doctoc $< --title "## Table of Contents" > /dev/null 24 | 25 | build/index.html: storage-access.bs Makefile 26 | mkdir -p build 27 | bikeshed --die-on=warning spec $< $@ 28 | 29 | build/images/storage-access-prompt.png: images/storage-access-prompt.png Makefile 30 | mkdir -p build/images 31 | cp images/storage-access-prompt.png build/images 32 | -------------------------------------------------------------------------------- /explainers/fedcm-saa-privacy-questionnaire.md: -------------------------------------------------------------------------------- 1 | # Security / Privacy Questionnaire for FedCM as a trust signal for the Storage Access API 2 | 3 | > 01. What information does this feature expose, 4 | > and for what purposes? 5 | 6 | This feature exposes no additional information to websites. It allows successful grants of SAA calls when a prior FedCM permission has been allowed by the user. Both APIs allow for cross-site identification, but are gated on user permission. 7 | 8 | > 2. Do features in your specification expose the minimum amount of information 9 | > necessary to implement the intended functionality? 10 | 11 | Yes, no additional information is exposed. 12 | 13 | > 5. Do the features in your specification expose personal information, 14 | > personally-identifiable information (PII), or information derived from 15 | > either? 16 | 17 | Not in itself, though again this feature integrates two existing APIs that are frequently used to transmit such information across sites (gated on user permission). 18 | 19 | > 7. How do the features in your specification deal with sensitive information? 20 | 21 | It doesn't in itself. 22 | 23 | > 8. Does data exposed by your specification carry related but distinct 24 | > information that may not be obvious to users? 25 | 26 | No 27 | 28 | > 10. Do the features in your specification introduce state 29 | > that persists across browsing sessions? 30 | 31 | No, it uses existing permission state from FedCM grants. 32 | 33 | > 12. Do the features in your specification expose information about the 34 | > underlying platform to origins? 35 | 36 | No 37 | 38 | > 14. Does this specification allow an origin to send data to the underlying 39 | > platform? 40 | 41 | No 42 | 43 | > 16. Do features in this specification enable access to device sensors? 44 | 45 | No 46 | 47 | > 17. Do features in this specification enable new script execution/loading 48 | > mechanisms? 49 | 50 | No 51 | 52 | > 18. Do features in this specification allow an origin to access other devices? 53 | 54 | No 55 | 56 | > 19. Do features in this specification allow an origin some measure of control over 57 | > a user agent's native UI? 58 | 59 | No 60 | 61 | > 20. What temporary identifiers do the features in this specification create or 62 | > expose to the web? 63 | 64 | None it itself. 65 | 66 | > 21. How does this specification distinguish between behavior in first-party and 67 | > third-party contexts? 68 | 69 | It follows the existing behavior of SAA and FedCM in 1P / 3P contexts 70 | 71 | > 22. How do the features in this specification work in the context of a browser’s 72 | > Private Browsing or Incognito mode? 73 | 74 | See https://github.com/privacycg/storage-access/blob/main/tag-security-questionnaire.md for how SAA handles private / incognito mode. 75 | 76 | > 24. Does this specification have both "Security Considerations" and "Privacy 77 | > Considerations" sections? 78 | 79 | Yes 80 | 81 | > 26. Do features in your specification enable origins to downgrade default 82 | > security protections? 83 | 84 | Not beyond how SAA already allows for downgrading the security protections afforded by third-party cookie blocking. 85 | 86 | > 28. What happens when a document that uses your feature is kept alive in BFCache 87 | > (instead of getting destroyed) after navigation, and potentially gets reused 88 | > on future navigations back to the document? 89 | 90 | This feature uses long-lived FedCM grants and as such is intended to be usable in future documents or future navigations to the same document. 91 | 92 | > 30. What happens when a document that uses your feature gets disconnected? 93 | 94 | This feature simply adds an additional trust parameter for allowing SAA grants, so this shouldn't be a consideration. 95 | 96 | > 32. Does your feature allow sites to learn about the users use of assistive technology? 97 | 98 | No 99 | 100 | > 34. What should this questionnaire have asked? 101 | -------------------------------------------------------------------------------- /tag-security-questionnaire.md: -------------------------------------------------------------------------------- 1 | 01. What information might this feature expose to Web sites or other parties, 2 | and for what purposes is that exposure necessary? 3 | 4 | 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. 5 | 6 | A nested Document gains access to the same cookies it has as the active document of a top-level browsing context when it calls `document.requestStorageAccess()` and is returned a resolving Promise (however, `SameSite` restrictions will still apply, i.e., `SameSite=Lax` or `Strict` cookies will not be available, which is important for security but doesn't affect the privacy considerations). 7 | With these cookies it can authenticate itself to the server and load user-specific information. 8 | 9 | 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. 10 | 11 | 02. Do features in your specification expose the minimum amount of information 12 | necessary to enable their intended uses? 13 | 14 | Where possible, yes: 15 | 16 | - Permission grants for storage access are double-keyed on a (site, site) basis, meaning that requesting documents will not be able to access cross-site cookies outside of the top-level site that they were granted access under. 17 | 18 | - To a single requesting document, this feature allows access to all its cross-site cookies (if given permission), which may or may not exceed the minimum necessary amount of information that document needs. However, given the flexible nature of cookies it is very hard to determine which cookies a document needs and developer flexibility is an explicit goal of the API. Also, from a privacy perspective, passing any single high-entropy identifier such as a cookie across the site boundary is equivalent to full cross-site cookie access. 19 | 20 | - For security reasons, this API applies a few restrictions to how much information is exposed to a site that is granted storage access: 21 | - Each storage access grant applies only to the nested document that requested it. Other documents have to re-request access, to ensure explicit opt-in from endpoints and prevent attacks from embedders. 22 | - This specification calls out that implementers should still follow SameSite rules when attaching cross-site cookies with storage access. This API does not intend to waive existing security protections. 23 | 24 | 03. How do the features in your specification deal with personal information, 25 | personally-identifiable information (PII), or information derived from 26 | them? 27 | 28 | As mentioned, the SAA enables sharing of information through cross-site cookies, but does not expand on that or deal directly with PII in any way. 29 | 30 | 04. How do the features in your specification deal with sensitive information? 31 | 32 | See above. 33 | 34 | 05. Do the features in your specification introduce new state for an origin 35 | that persists across browsing sessions? 36 | 37 | Yes, a new "storage-access" permission that is managed via the permissions API and is double-keyed on (site, site). This should make it impossible for sites to access the new state across different top-level contexts. 38 | 39 | 06. Do the features in your specification expose information about the 40 | underlying platform to origins? 41 | 42 | No 43 | 44 | 07. Does this specification allow an origin to send data to the underlying 45 | platform? 46 | 47 | No 48 | 49 | 08. Do features in this specification enable access to device sensors? 50 | 51 | No 52 | 53 | 09. Do features in this specification enable new script execution/loading 54 | mechanisms? 55 | 56 | No 57 | 58 | 10. Do features in this specification allow an origin to access other devices? 59 | 60 | No 61 | 62 | 11. Do features in this specification allow an origin some measure of control over 63 | a user agent's native UI? 64 | 65 | While showing UI for storage access prompts is left largely implementation-defined, this API can generally be expected to enable origins to spawn permission prompts detailing the top-level site and the embedded site in the UI. 66 | 67 | We have added a number of anti-abuse, spam and annoyance protections as outlined in the security considerations of the spec. 68 | 69 | 12. What temporary identifiers do the features in this specification create or 70 | expose to the web? 71 | 72 | None 73 | 74 | 13. How does this specification distinguish between behavior in first-party and 75 | third-party contexts? 76 | 77 | This specification is meant to be used in third-party contexts, i.e. in cross-site contexts (see https://tess.oconnor.cx/2020/10/parties). While the JS API itself will be accessible in a same-site context, it will resolve successfully by default without setting a storage access permission, to inform the developer that storage should be accessible by virtue of being in a same-site context. As such, the storage-access permission is not meaningful for any storage restrictions in same-site contexts (whatever those may look like). 78 | 79 | 14. How do the features in this specification work in the context of a browser’s 80 | Private Browsing or Incognito mode? 81 | 82 | The specification currently makes no explicit recommendation, as preferences may differ between user agents. It can generally be expected that in most user agents cross-site cookies are disabled in Private Browsing contexts, which would make it a natural fit for SAA. However, exposing prompts to users in private browsing that request sharing of data between two sites may be viewed as intrusive. The API includes sufficient mechanisms for user agents to always deny storage access requests in private browsing. 83 | 84 | 15. Does this specification have both "Security Considerations" and "Privacy 85 | Considerations" sections? 86 | 87 | Yes 88 | 89 | 16. Do features in your specification enable origins to downgrade default 90 | security protections? 91 | 92 | To some extent, yes, albeit with strong controls to prevent accidental loss of protection. Deprecation of cross-site cookies prevents certain class of attacks as [outlined in detail in this document](https://docs.google.com/document/d/1AsrETl-7XvnZNbG81Zy9BcZfKbqACQYBSrjM3VsIpjY/edit#heading=h.vb3ujl8dnk4q). 93 | 94 | We apply the security restrictions as mentioned in point 2), specifically requiring strict per-document opt-in through calling rSA(), to avoid this loss of security. 95 | 96 | 17. How does your feature handle non-"fully active" documents? 97 | 98 | It will reject calls to rSA and hSA with an exception, as detailed in the spec. 99 | 100 | 18. What should this questionnaire have asked? 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Storage Access API 2 | 3 | A [Work Item](https://privacycg.github.io/charter.html#work-items) of 4 | the [Privacy Community Group](https://privacycg.github.io/). 5 | 6 | ## Editors: 7 | 8 | - Benjamin VanderSloot 9 | - Johann Hofmann 10 | - Anne van Kesteren 11 | 12 | ## Participate 13 | - https://github.com/privacycg/storage-access/issues 14 | 15 | 16 | 17 | ## Table of Contents 18 | 19 | - [Introduction](#introduction) 20 | - [Motivating Use Cases](#motivating-use-cases) 21 | - [Social Network Commenting Widget](#social-network-commenting-widget) 22 | - [Third-Party Payment Provider](#third-party-payment-provider) 23 | - [Subscribed Video Service](#subscribed-video-service) 24 | - [Non-Goals](#non-goals) 25 | - [The API](#the-api) 26 | - [hasStorageAccess](#hasstorageaccess) 27 | - [requestStorageAccess](#requeststorageaccess) 28 | - [Scope of Storage Access](#scope-of-storage-access) 29 | - [Key scenarios](#key-scenarios) 30 | - [The User Is Not Yet Logged In To the Embedee](#the-user-is-not-yet-logged-in-to-the-embedee) 31 | - [The User Opts Out](#the-user-opts-out) 32 | - [Detailed Design Discussion](#detailed-design-discussion) 33 | - [Recovery Path](#recovery-path) 34 | - [Timeout of an Opt In](#timeout-of-an-opt-in) 35 | - [Compatibility Measure](#compatibility-measure) 36 | - [Considered Alternatives](#considered-alternatives) 37 | - [Automatically Grant Access to Websites Used Often](#automatically-grant-access-to-websites-used-often) 38 | - [Automatically Grant Access Upon User Interaction](#automatically-grant-access-upon-user-interaction) 39 | - [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition) 40 | - [References & Acknowledgements](#references--acknowledgements) 41 | 42 | 43 | 44 | ## Introduction 45 | 46 | Browsers may block third-party resources from accessing cookies and other storage for privacy and security reasons. The 47 | most popular reason is cross-site tracking prevention. Such blocking breaks authenticated cross-site embeds such as 48 | commenting widgets, embedded payment providers, and subscribed video services. 49 | 50 | The Storage Access API provides a means for authenticated cross-site embeds to check their blocking status and request 51 | access to storage if they are blocked. 52 | 53 | ## Motivating Use Cases 54 | 55 | These use cases assume that the third-party is blocked from cookie access and possibly other storage too. 56 | 57 | ### Social Network Commenting Widget 58 | The user is logged in to Social Network Example with domain name social.example and is now visiting blog.example which 59 | allows readers to comment on blogposts using their social.example account. The user taps/clicks in the commenting 60 | widget, a cross-site iframe from social.example, to make a comment. The onclick event handler in the iframe calls the 61 | Storage Access API to request cookie access needed to authenticate the user cross-site. The user has not commented on 62 | this blog before and thus gets prompted to allow or disallow storage access, decides to allow storage access, and 63 | proceeds to comment on the blogpost. 64 | 65 | ### Third-Party Payment Provider 66 | The user is logged in to Payments Example with domain name payments.example and is now shopping on clothes.example. They 67 | decide to check out and pick Payments Example as their preferred way of paying. The Payments Example option is served 68 | through a cross-site iframe from payments.example that calls the Storage Access API upon the tap/click to pick it as 69 | payment option. The user has used Payments Example previously on this shopping site and thus already been prompted for 70 | storage access. Therefore, the user is not prompted again, the iframe is automatically granted storage access, and 71 | the user proceeds to fulfill the payment. 72 | 73 | ### Subscribed Video Service 74 | The user is logged in to Online Videos Example with domain name videos.example and is paying for an ad-free video 75 | experience. Now they are on games.example and want to watch a video of an exciting game play. The video is served in a 76 | cross-site iframe from videos.example and the iframe calls the Storage Access API upon the tap/click on its play button. 77 | The user has used Online Videos previously on this gaming site and thus is not prompted, the iframe is automatically 78 | granted storage access, and the user proceeds to watch the video. 79 | 80 | ## Non-Goals 81 | 82 | The Storage Access API is not intended to grant arbitrary third-parties cookie access. It is only intended to grant 83 | cookie access to third parties that the user actively uses as first party too, i.e. websites the user recognizes and 84 | uses. 85 | 86 | The Storage Access API can be used for many more things than authenticated embeds, for instance single sign-on, 87 | cross-site subscription services, and federated logins. However, those are not the primary goals of this API and thus, 88 | requirements that are serving those use cases but not the authenticated embed use case might not be met by the 89 | Storage Access API. That said, the Storage Access API is not in conflict with single sign-on, cross-site subscription 90 | services, and federated logins. 91 | 92 | The Storage Access API is not a gateway into a legacy or quirks mode with which third-parties request permission to 93 | get back to a state similar to before e.g. tracking prevention features. Concretely, granted storage access should 94 | not be interpreted as an "allow cross-site tracking mode" or "make old things work mode." The API is opt-in and is 95 | intended for modern scenarios that are created under the assumption of no third-party storage access by default. 96 | 97 | ## The API 98 | 99 | The Storage Access API lives under the document object since it controls document.cookie and the scope of the storage 100 | access may be tied to the scope of the document. 101 | 102 | ### hasStorageAccess 103 | 104 | ```js 105 | var promise = document.hasStorageAccess(); 106 | promise.then( 107 | function (hasAccess) { 108 | // Boolean hasAccess says whether the document has access or not. 109 | }, 110 | function (reason) { 111 | // Promise was rejected for some reason. 112 | } 113 | ); 114 | ``` 115 | 116 | ### requestStorageAccess 117 | 118 | ```html 119 | 132 | 133 | ``` 134 | 135 | ### Scope of Storage Access 136 | 137 | If an iframe is granted storage access through the API, only that calling iframe and its subresources should have 138 | access to storage. 139 | 140 | The length in time of storage access is up to the browser. It could be for: 141 | - The lifetime of the frame as long as it's hosting content from the same website. 142 | - The lifetime of the top page. 143 | - The lifetime of the browsing session. 144 | - A certain amount of calendar time such as seven days. 145 | 146 | It should be noted that since only the calling iframe gets storage access, it's hard to grant storage access across 147 | browsing sessions or page loads. 148 | 149 | ## Key scenarios 150 | 151 | ### The User Is Not Yet Logged In To the Embedee 152 | 153 | In the case of the user not being logged in to the embeddee, there it should be possible for the iframe to make use of 154 | the user gesture that was required to call document.requestStorageAccess() to also do a popup to enable the user to log 155 | in. 156 | 157 | ### The User Opts Out 158 | 159 | In the case of the user being prompted for storage access and explicitly opts out, the requesting iframe should not be 160 | able to prompt again or do a popup without receiving another user gesture. 161 | 162 | ## Detailed Design Discussion 163 | 164 | ### Recovery Path 165 | 166 | If the user explicitly opts out when prompted for storage access, they may find themselves in a situation they don't 167 | like or didn't expect, such as no ability to comment on a blogpost or ads in a video feed from a service they pay for to 168 | be ad-free. In short, there needs to be a recovery path. 169 | 170 | One way of dealing with this is to allow at least two prompts per embed. If the user explicitly opts out twice, it's a 171 | done deal. 172 | 173 | Another way of dealing with this is to offer affordances to reset choices in browser settings. 174 | 175 | ### Timeout of an Opt In 176 | 177 | If the user explicitly grants storage access to an embedee, the question is for how long that grant should last before a 178 | new prompt is shown? Options include 1) as long as the user keeps re-engaging with the embedee on an 179 | hourly/daily/weekly/monthly basis, 2) with a static timeout of e.g. 30 days, or 3) only for the lifetime of the embedded 180 | document. 181 | 182 | ### Compatibility Measure 183 | 184 | A compatibility measure that has proven to be effective in the wild (shipping in Safari and Firefox) is to automatically 185 | open up page-wide storage access for a third-party that opens a popup through ```window.open()``` and receives user 186 | interaction in that popup. 187 | 188 | This page-wide scope is a good example of a legacy mode of sorts which is not the intention of the Storage Access API, 189 | as explained in [Non-Goals](#non-goals). 190 | 191 | ## Considered Alternatives 192 | 193 | There are some possible alternatives. 194 | 195 | ### Automatically Grant Access to Websites Used Often 196 | 197 | Instead of requiring an explicit API call, the browser could keep track of which websites the user engages with a lot, 198 | possibly even know which websites the user is logged in to, and grant storage access to those websites automatically. 199 | 200 | However, it may be undesirable to allow global cross-site authentication by a third party based on activity on a 201 | first-party website. In fact, such blanket cross-site authentication/identification is often what blocking of 202 | third-party cookies and storage is trying to avoid. 203 | 204 | ### Automatically Grant Access Upon User Interaction 205 | 206 | Instead of requiring an explicit API call, the browser could grant storage access to embedded third-party iframes upon 207 | user interaction with the iframe. This allows for blocking of third-party cookies in all passive scenarios such as 208 | pure page loads and scrolling. 209 | 210 | However, such behavior may incentivize third-parties to render iframes solely for the purposes of getting cookie access, 211 | for instance through invisible overlay iframes à la Clickjacking or through iframes that look like first-party content. 212 | 213 | ## Stakeholder Feedback / Opposition 214 | 215 | - Safari : Shipping 216 | - Firefox : Shipping 217 | - Edge : Implementing 218 | - Brave : Positive 219 | - Chrome : Shipping 220 | 221 | ## References & Acknowledgements 222 | 223 | Several people have provided valuable feedback already in the 224 | [WHATWG HTML issue](https://github.com/whatwg/html/issues/3338) filed on Jan 10, 2018. We're thankful for all that 225 | engagement. 226 | -------------------------------------------------------------------------------- /explainers/storage-access-for-fedcm.md: -------------------------------------------------------------------------------- 1 | # FedCM as a trust signal for the Storage Access API 2 | 3 | ## Authors 4 | 5 | * [Johann Hofmann](https://github.com/johannhof) 6 | * [Chris Fredrickson](https://github.com/cfredric) 7 | * [Yi Gu](https://github.com/yi-gu) 8 | 9 | ## Table of Contents 10 | 11 | - [Introduction](#introduction) 12 | - [Goals](#goals) 13 | - [Non-goals](#non-goals) 14 | - [Motivation](#motivation) 15 | - [Example Use cases](#example-use-cases) 16 | - [API Details](#api-details) 17 | - [Considered alternatives](#considered-alternatives) 18 | - [Privacy Considerations](#privacy-considerations) 19 | - [Security Considerations](#security-considerations) 20 | 21 | ## Introduction 22 | 23 | When a user consents to using their identity with a 3rd party Identity Provider (IdP) on a Relying Party (RP), many IdPs require third-party cookies to function correctly and securely. Currently, while these IdPs might prefer to use [FedCM](https://github.com/fedidcg/FedCM) for an overall better user experience, they would not be able to for lack of cookie access and would have to show the more generic [Storage Access API](https://github.com/privacycg/storage-access) (SAA) prompts instead. 24 | 25 | This proposal aims to satisfy that requirement in a private and secure manner by updating the Storage Access API (SAA) permission checks to not only accept the permission grant that is given by a storage access prompt, but also the permission grant that is given by a FedCM prompt. 26 | 27 | A key property of this mechanism is limiting the grant to cases explicitly allowed by the RP via the FedCM permissions policy, enforcing a per-frame control for the RP and preventing passive surveillance by the IdP beyond the capabilities that FedCM already grants, as outlined in the [Privacy Considerations](#privacy-considerations). 28 | 29 | Where there are no negative implications for user privacy and security, we propose following the more flexible double-keyed per-site grant scope of SAA vs. the triple-keyed per-origin scope of FedCM as described in the [API Details](#api-details), to maximize developer utility. 30 | 31 | ## Goals 32 | 33 | 34 | * Improve the user experience by enabling cookie-based identity / sign-in use cases to use FedCM. 35 | * Allow developers to avoid explicit token handling and the usage of HttpOnly and Secure attributes via cookies, to reduce susceptibility to cookie theft attacks. 36 | * Securely mediate access to cookies via FedCM through explicit Storage Access opt-in. 37 | 38 | 39 | ## Non-Goals 40 | * Change the privacy and security model behind a FedCM grant. Accepting the prompt should have the same privacy and security implications to a user as before. Specifically, any continued access to cross-site data for an IdP after a FedCM grant should require [active collaboration from an RP](#privacy-considerations). 41 | * Deprecate Storage Access API prompts. This proposal adds an additional means of utilizing the SAA, we do not propose removing other ways of setting storage access permissions, such as the UI shown by browsers as part of a “traditional” storage access request. It’s important to preserve this shared prompt for web interop and we encourage developers with cross-site cookie access needs to fall back to it where possible. 42 | 43 | ## Motivation 44 | 45 | [FedCM](https://fedidcg.github.io/FedCM/) is an API that mediates federated user identity flows through the application of (ideally) well-understood, purpose-driven user interfaces. Using the `navigator.credentials` API, it exposes a high-entropy user identifier (token) from an IdP to an RP. 46 | 47 | This kind of token based authentication/authorization is commonly used in federated identity flows, which FedCM intends to address. Other login schemes, particularly for Single-Sign-On (SSO), rely on the presence of (cross-site) cookies as explained in the examples below. 48 | 49 | These kind of flows can currently be solved by the Storage Access API, which mediates permission for documents to access cross-site cookies. However, as SAA lacks additional context on the nature of the request for cross-site cookie access, its UI in browsers tends to be very generic and unintuitive. It’s also primarily designed to solve use cases for authenticated embeds, which makes it difficult to fit seamlessly onto SSO flows (without sacrificing some of the anti-abuse mechanics of SAA such as the prior user gesture requirement). 50 | 51 | Due to its arguably much more intuitive user experience for mediating user identity, FedCM would be a good fit to cover these login-oriented use cases instead. To do this, it needs to be compatible with identity flows that require cookies to work. This is where a well-designed integration with SAA can help, by providing secure access to cross-site cookies based on FedCM grants as an additional trust signal. 52 | 53 | 54 | ### Example Use Cases 55 | 56 | These are examples we’ve found in our work with partners on resolving breakage from the deprecation of third-party cookies, but it’s important to note that this list does not exhaustively describe the utility a flexible integration such as the one proposed here would bring to developers. 57 | 58 | 59 | #### SSO through SAML in subresources 60 | 61 | Consider dashboard.example, an enterprise application which provides a centralized integration of various custom views from third-party analytics vendors. It does this by displaying a number of cross-site iframes (called embed.example in the below graphic) that contain relevant business information for the user, and as such need to be gated on some user authentication. Authenticating the user to these individual iframes is done seamlessly via an IDP (idp.example), which the user initially logs into in a top-level context. 62 | 63 | ![image](https://github.com/explainers-by-googlers/storage-access-for-fedcm/assets/2622601/6aa831f2-5a4d-472b-87d7-e702c3f8e59e) 64 | 65 | 66 | During the initial login flow, the IDP stores the user identity in a SameSite=None cookie and can now function as an SSO provider, commonly via brief redirects to idp.example which returns a SAML response (or similar) to the originally requested site. This works well in a top-level context, where idp.example has access to its first-party cookies, but third-party cookie blocking prevents it from working in embedded contexts. 67 | 68 | This is an issue for dashboard.example, as each of its embeds needs to authenticate through a redirect to idp.example. 69 | 70 | ![image](https://github.com/explainers-by-googlers/storage-access-for-fedcm/assets/2622601/129517f1-cf31-4bca-8c9b-3a383dfca2c1) 71 | 72 | With this proposal, dashboard.example could initially authenticate with idp.example via FedCM. Then, assuming the user grants this request, idp.example would be allowed to use SAA (possibly via [storage access headers](https://github.com/cfredric/storage-access-headers)) to enable its identity flow. 73 | 74 | ![image](https://github.com/explainers-by-googlers/storage-access-for-fedcm/assets/2622601/a81017fa-8599-4cfb-8905-9b3aaca5004a) 75 | 76 | Switching to a cookie-less flow would have a number of drawbacks for idp.example in this case, notably the loss of cookie-specific security mechanics such as HttpOnly, the departure from established authentication patterns and the need to update third-party software that integrates with their services. 77 | 78 | 79 | #### Iframe-based token refresh 80 | 81 | For access control reasons, some IdPs want to avoid exposing long-lived and/or permissive tokens to its RPs through FedCM credentials. Instead, the IdP maintains an iframe embedded in the RP page, which periodically refreshes tokens and transmits them to the RP for usage as API tokens, encryption keys, etc. 82 | 83 | ![image](https://github.com/explainers-by-googlers/storage-access-for-fedcm/assets/2622601/3aa13dbf-8132-4ae0-b37c-43eb2799c8cc) 84 | 85 | 86 | However, in order to perform this refresh, the IdP still needs to retain access to some longer lasting authentication token. This is typically done through the use of third-party cookies, which have excellent security properties as they’re accessible to the embedded IdP but not readable by its embedder or any (injected) scripts on the page. 87 | 88 | 89 | ## API Details 90 | 91 | It’s important to note that we are not proposing any new web API surface, but instead a behavior change for the Storage Access API to consider FedCM grants. As such, the code examples might not reveal a lot of information about the desired effect of this proposal. 92 | 93 | 94 | ### Current FedCM / SAA functionality 95 | 96 | Today, using FedCM, RPs can call FedCM to establish a persistent cross-site connection between themselves and specific IDPs. 97 | 98 | ```javascript 99 | // In RP top-level document, where RP and IDP are cross-site: 100 | 101 | // Ensure FedCM permission has been granted. 102 | const cred = await navigator.credentials.get({ 103 | identity: { 104 | providers: [{ 105 | // normal IDP config, elided here. 106 | }], 107 | }, 108 | mediation: 'optional', // default mediation mode 109 | }); 110 | ``` 111 | 112 | On initial use, this will prompt the user for permission via sign-in prompt mediated by the browser. The RP will use the FedCM API to sign-in the user (if needed), which creates the "connection" between the RP and IDP in the `connected accounts set` internally. This connection is keyed by the RP origin (the top-level origin), the IDP origin, the origin of the embedder when used in an iframe, and the account identifier. 113 | 114 | As a result, cross-site credentials are returned and future invocations of `navigator.credentials.get() `under the same set of keys will not require browser mediation / user permission. 115 | 116 | At this point, many IDPs have a need for access to 3rd party cookies, which, from a privacy perspective, would not exceed the capabilities already granted to them through the ability to exchange high-entropy credentials, but presents greater flexibility, security and a more powerful API. 117 | 118 | To do this at the moment, they would have to invoke `document.requestStorageAccess()` which requires a new user gesture and spawns _another_ user permission prompt (they just accepted the FedCM prompt!) 119 | 120 | 121 | ### SAA Autogrants 122 | 123 | Instead of showing this additional prompt, we propose that `document.requestStorageAccess() `take into consideration the `connected accounts set` that contains information about which RPs and IDPs are allowed to exchange cross-site information and considers it equivalent to the presence of a [storage-access](https://privacycg.github.io/storage-access/#permissiondef-storage-access) permission. When an RP/IDP pair has been approved for access to cross-site credentials, taking into account FedCM concepts such as [prevent silent access](https://w3c.github.io/webappsec-credential-management/#origin-prevent-silent-access-flag), it is also allowed storage access. 124 | 125 | This comes without additional user activation or prior top-level user interaction requirements as it is treated like a granted permission (which seems most accurate given that the user has in fact interacted with a permission prompt from a FedCM IDP). 126 | 127 | **Note that the automatic grant will not lead to a new storage-access permission being created.** FedCM grants work slightly differently (see below) and have different lifetimes, making it hard to convert them directly to storage-access permissions. It’s also undesirable for the user (agent) to maintain two separate permissions that grant a similar capability. 128 | 129 | Instead, the `document.requestStorageAccess()` call would set the environment’s [has storage access](https://privacycg.github.io/storage-access/#environment-has-storage-access) flag to true (i.e. [processing the FedCM grant as a permission state](https://privacycg.github.io/storage-access/#the-document-object:~:text=Let-,process%20permission%20state,-be%20an%20algorithm)), granting the iframe document access to cross-site cookies. 130 | 131 | ```javascript 132 | // In RP top-level document, where RP and IDP are cross-site: 133 | 134 | // Ensure FedCM permission has been granted. 135 | const cred = await navigator.credentials.get({ 136 | identity: { 137 | providers: [{ 138 | // normal IDP config, elided here. 139 | }], 140 | }, 141 | mediation: 'optional', 142 | }); 143 | 144 | // In an embedded IDP iframe: 145 | 146 | // No user gesture is needed to call this, and the call will be auto-granted. 147 | await document.requestStorageAccess(); 148 | // This returns “true”. 149 | const hasAccess = await document.hasStorageAccess(); 150 | ``` 151 | 152 | This same check may be performed by proposed SAA extensions such as [requestStorageAccessFor](https://github.com/privacycg/requestStorageAccessFor) and [Storage Access Headers](https://github.com/cfredric/storage-access-headers) to enable additional flexibility for web developers. 153 | 154 | 155 | ### Dealing with scope differences 156 | 157 | One complication to this idea is the fact that FedCM is scoped by origin, while SAA grants are scoped to site. FedCM also [differs in its permission keying](https://fedidcg.github.io/FedCM/#issue-d2fd6198) (which is to be specified). While storage-access permissions are keyed by (top-level, embed), FedCM grants are keyed by (top-level, embedder (RP), IDP). 158 | 159 | This decision makes sense for FedCM as the tokens it mediates are directly returned to the caller of `navigator.credentials.get()`, and as such should be same-origin restricted by default. Additionally, merely double-keying by (RP, IDP) would allow an attacker iframe embedded in rp.example extract information about an adjacent idp.example using FedCM. 160 | 161 | This restriction is not needed for SAA, as the information it mediates comes from cookies, where cross-subdomain sharing is an opt-in feature (via the Domain attribute), or [other storage types](https://github.com/arichiv/saa-non-cookie-storage/) which are already same-origin by default. This means that any same-site cross-origin sibling of the site calling FedCM will only have access to its own storage or explicitly registrable domain-scoped cookies. 162 | 163 | There are still attacks (such as CSRF and click-jacking) and cross-site leaks which try to take advantage of this credentialed state without directly reading it. SAA, through its explicit opt-in mechanisms, puts embed developers at a much lower risk of being exposed to such an attack. 164 | 165 | Although there is ostensibly little privacy or security benefit as shown above, with this proposal, we make it possible for the FedCM and SAA integration to honor the stricter scope of FedCM. Put differently, `document.requestStorageAccess() `auto-grants could be restricted to contexts in which RP and IDP are same-origin to their entries in the `connected accounts set`. 166 | 167 | ```javascript 168 | // In top-level rp.example: 169 | 170 | // Ensure FedCM permission has been granted. 171 | const credential = await navigator.credentials.get({ 172 | identity: { 173 | providers: [{ 174 | configURL: "https://accounts.idp.example/manifest.json", 175 | clientId: "123", 176 | }] 177 | } 178 | }); 179 | 180 | // In an embedded accounts.idp.example iframe, this call will automatically grant. 181 | await document.requestStorageAccess(); 182 | 183 | 184 | // In an embedded idp.example iframe, should this automatically grant as well? 185 | await document.requestStorageAccess(); 186 | ``` 187 | 188 | The result of that choice would be that users could encounter situations where an embedded call to `document.requestStorageAccess()`is not automatically granted despite a FedCM grant being present for a (top-level site, embed site) pair (and the user having seen and accepted the FedCM prompt). 189 | 190 | Overall, there seem to be 3 different options to pursue: 191 | 192 | 193 | 194 | 1. (Preferred) Grant storage-access with a wider scope than FedCM, by extracting the corresponding (top-level site, embedded site) pair from the FedCM grant. 195 | 196 | This gives the greatest amount of developer flexibility and incurs no additional privacy risks (given that (partitioned) cookies can already be site scoped) and little security risk as explained above. 197 | 198 | 199 | 200 | 2. Expose additional parameters for RP and IDP to control the scope of storage access. 201 | 202 | This would require changes in the FedCM API and increase overall complexity for developers. It also wouldn’t effectively protect the origin boundary given that any developer with a need to increase the scope to site could easily opt into it. 203 | 204 | 205 | 206 | 3. Simply match the scope of the FedCM grant and fall back to default SAA processing of a request (usually show a prompt) when it does not match. 207 | 208 | This preserves the original FedCM scope, but could lead to additional SAA prompts showing in some cases. Another drawback is the developer-facing inconsistency of different storage access grants and the need to manage different types of storage access grants. 209 | 210 | In the end, option 3 could prevent developers from building useful cross-site integrations and/or force them to show an SAA prompt with the same sites that the user just consented to via FedCM, which seems like a jarring user experience (or not use FedCM at all). Hence, we recommend option 1. 211 | 212 | 213 | ### Interaction with Permissions Policy 214 | 215 | As explained in a lot of detail in the [Privacy Considerations](#privacy-considerations), an important property of this proposal is that it does not regress on the existing privacy guarantees of the FedCM API. One such guarantee is that the RP stays in control over when an IdP can access cross-site information via tokens through calling navigator.credentials.get(). This happens via the ["identity-credentials-get" Permissions Policy](https://fedidcg.github.io/FedCM/#permissions-policy-integration), which requires an opt-in by the RP. 216 | 217 | To restrict storage access to only succeed when an IdP would be otherwise able to access cross-site credentials via navigator.credentials.get(), we propose that **both** the "identity-credentials-get" permission policy and the "storage-access" permission policy will be considered by the storage access integration. 218 | 219 | This would require iframes which intend to use SAA with FedCM to have the `allow="identity-credentials-get"` attribute and **not** have an `allow="storage-access` none” (or similar) attribute. 220 | 221 | As a side effect, this also gives RPs the ability to explicitly disallow IdPs from using Storage Access, though we haven’t identified a use case for this. 222 | 223 | 224 | ## Considered Alternatives 225 | 226 | * **Have FedCM set a new storage-access permission upon user grant.** 227 | As an alternative approach, upon successful grant, FedCM could simply set a new storage-access permission scoped to the respective (top-level site, embedded site) pair (i.e. RP and IDP). We discarded this idea as it has a few drawbacks: 228 | 229 | * Creating two simultaneous permissions / grants for a similar capability. This seems confusing for the user. 230 | * Relatedly, developers would now have to manage the permission for SAA, including different permission expiry from the FedCM grant. 231 | * This would be necessarily tied to the (broader) SAA permission scope. 232 | * This would be much harder to regulate via the “identity-credentials-get” permissions policy. 233 | * **Modify requestStorageAccess to accept a credential object, and auto-grant SAA permission if the credential contains appropriate metadata.** 234 | This was an initial idea to ensure storage access it strictly tied to the availability of an identity credential. It seems unnecessarily complicated, given that the current proposal is able to enact the same scope without this measure. 235 | 236 | ## Privacy Considerations 237 | 238 | ### RP Control over IDP Storage Access 239 | 240 | For both FedCM and the Storage Access API, their primary consideration for user privacy is the ability to link the user identity between two sites. In the case of FedCM, this happens through sharing a high-entropy identifier, with Storage Access API, shared identity can be established through the re-introduction of cross-site cookies for the embedded 3rd party (the IDP). 241 | 242 | Both APIs are built for this purpose, and as such come with their own mechanisms (in most cases, prompts), to ensure sufficient user consent. 243 | 244 | Does that mean that both exhibit the same risks to the user when one or more parties in the process are participating in cross-site tracking? It depends on the threat model we use. 245 | 246 | A commonly applied threat model posits that both sharing a high-entropy identifier between two sites A and B, and permanent access to cross-site cookies for site B under A, is equivalent, because we assume possible coordination between A and B. When both parties are cooperating, a single high-entropy identifier can be used to establish a communication channel to transfer any amount of information between both parties, at any time (until the user clears all browser state for one of the parties, ignoring fingerprinting). 247 | 248 | However, if we apply a different threat model and assume that some RPs may choose to not cooperate with an IDP to enable cross-site recognition, then they can use FedCM with a single call to navigator.credentials.get(), which will only connect the RP to the IDP a single time, and is thus more private than the permanent ability to make credentialed requests to a cross-site resource. The RP could then even continue to embed iframes from the IdP as long as the ”identity-credentials-get” permissions policy is not set on those. 249 | 250 | There are still a few practical concerns with this idea: 251 | 252 | * In practice, IDPs usually coerce or enforce cooperation, e.g. by providing their own first-party scripts to run on a given RP. 253 | * The existence of partitioned state (e.g. CHIPS) or access to first-party state allows embedded 3Ps to sustain one-time identifiers indefinitely. 254 | 255 | Nonetheless, we propose restricting an IDP’s default access to instances where an RP allows this [through use of the Permissions Policy API](#interaction-with-permissions-policy). This requires explicit RP collaboration for continued access to cross-site state. 256 | 257 | This particularly helps protect against attacks where a large IDP may run additional embedded widgets (a social comment widget, an embedded maps widget, etc.) and would be able to use these to passively track users long after FedCM credentials were passed. 258 | 259 | 260 | ### Consistent user experience 261 | 262 | User agents should make FedCM grants and Storage Access permissions behave consistently, reflecting their similar capabilities, and ensure users understand the difference and similarities between the two. 263 | 264 | There are existing inconsistencies to deal with. In Chrome, the `connections `in the `connected accounts set` do not expire, while the `storage-access` permission expires after 30 days of disuse. This means that a user who grants FedCM permission is now granting perpetual access to unpartitioned cookies, whereas a user who grants storage access directly is only granting access for 30 days. Chrome currently also clears FedCM grants for sites when the user clears site data, but not Storage Access permissions. As user agents we should strive to reduce these inconsistencies where possible. 265 | 266 | Given that FedCM mediates a high-entropy token already, this does not change the privacy properties of this proposal, but is noteworthy nonetheless. 267 | 268 | ## Security Considerations 269 | As [previously discussed in the context of the Storage Access API](https://docs.google.com/document/d/1AsrETl-7XvnZNbG81Zy9BcZfKbqACQYBSrjM3VsIpjY/edit#heading=h.vb3ujl8dnk4q), opening up access to cross-site cookies when those cookies are otherwise blocked by default, comes with additional challenges for website security. 270 | 271 | This proposal does not substantially change the security considerations that already apply to the Storage Access API or FedCM. However, for the proposed integration with requestStorageAccessFor, it’s important to consider the ability for the RP to attack the IDP using [attacks on credentialed cross-site resources that aren't fully mitigated through CORS](https://github.com/privacycg/requestStorageAccessFor/issues/30). The recent [storage access headers](https://github.com/cfredric/storage-access-headers) proposal provides a better alternative that mitigates these attacks and as such the rSAFor integration should be considered with additional scrutiny and may not end up being necessary. 272 | -------------------------------------------------------------------------------- /storage-access.bs: -------------------------------------------------------------------------------- 1 |
  2 | Title: The Storage Access API
  3 | Shortname: storage-access
  4 | Repository: privacycg/storage-access
  5 | URL: https://privacycg.github.io/storage-access/
  6 | Editor: Benjamin VanderSloot, w3cid 135256, Mozilla https://mozilla.org, bvandersloot@mozilla.com
  7 | Editor: Johann Hofmann, w3cid 120436, Google https://google.com, johannhof@google.com
  8 | Editor: Anne van Kesteren, w3cid 38001, Apple Inc. https://apple.com, annevk@annevk.nl
  9 | Former Editor: John Wilander, w3cid 89478, Apple Inc. https://apple.com, wilander@apple.com
 10 | Former Editor: Theresa O’Connor, w3cid 40614, Apple Inc. https://apple.com, hober@apple.com
 11 | Abstract: The Storage Access API enables content in iframes to request access to website data (such as cookies).
 12 | Status Text: 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.
 13 | Text Macro: LICENSE Creative Commons Attribution 4.0 International License
 14 | Group: privacycg
 15 | Status: CG-DRAFT
 16 | Level: None
 17 | Markup Shorthands: markdown yes, css no
 18 | Complain About: accidental-2119 true
 19 | 
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 33 |
 34 | urlPrefix: https://fetch.spec.whatwg.org/; spec: Fetch
 35 |     text: http-network-or-cache fetch; url: #concept-http-network-or-cache-fetch; type: dfn
 36 |     text: http-redirect fetch; url: #concept-http-redirect-fetch; type: dfn
 37 | spec: RFC6265; urlPrefix: https://tools.ietf.org/html/rfc6265
 38 |     type: dfn
 39 |         text: cookie store; url: section-5.3
 40 | spec: RFC6265bis; urlPrefix: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-11
 41 |     type: dfn
 42 |         text: site for cookies; url: section-5.2.1
 43 | urlPrefix: https://w3c.github.io/webdriver/webdriver-spec.html#; spec: webdriver
 44 |     type: dfn
 45 |         text: current browsing context; url: dfn-current-browsing-context
 46 |         text: WebDriver error; url: dfn-error
 47 |         text: WebDriver error code; url: dfn-error-code
 48 |         text: extension command; url: dfn-extension-commands
 49 |         text: extension command URI template; url: dfn-extension-command-uri-template
 50 |         text: getting a property; url: dfn-getting-properties
 51 |         text: invalid argument; url: dfn-invalid-argument
 52 |         text: local end; url: dfn-local-end
 53 |         text: remote end steps; url: dfn-remote-end-steps
 54 |         text: unknown error; url: dfn-unknown-error
 55 |         text: unsupported operation; url: dfn-unsupported-operation
 56 |         text: session; url: dfn-session
 57 |         text: success; url: dfn-success
 58 | 
 59 | spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
 60 |     type: dfn
 61 |         text: source snapshot params; url: browsing-the-web.html#source-snapshot-params
 62 |         text: snapshotting source snapshot params; url: browsing-the-web.html#snapshotting-source-snapshot-params
 63 |         text: create navigation params by fetching; url: browsing-the-web.html#create-navigation-params-by-fetching
 64 |         text: set up a window environment settings object; url: nav-history-apis.html#set-up-a-window-environment-settings-object
 65 |         text: environment
 66 |         
 67 |         text: ancestry; for: environment; url: TODO
 68 | 
 69 | spec: fetch; urlPrefix: https://fetch.spec.whatwg.org/
 70 |     type: dfn
 71 |         for: response
 72 |             text: has-cross-origin-redirects; url: #response-has-cross-origin-redirects
 73 | 
 74 | spec: fedcm; urlPrefix: https://w3c-fedid.github.io/FedCM/
 75 |     type: dfn
 76 |         text: connected accounts set; url: browser-connected-accounts-set
 77 |         text: IDP; url: idp
 78 |         text: RP; url: rp
 79 | 
 80 | spec: credential-management-1; urlPrefix: https://w3c.github.io/webappsec-credential-management/
 81 |     type: dfn
 82 |         text: prevent silent access flag; url: origin-prevent-silent-access-flag
 83 | 
 84 | spec: permissions-policy; urlPrefix: https://w3c.github.io/webappsec-permissions-policy/
 85 |     type: dfn
 86 |         text: is feature enabled; url: algo-is-feature-enabled
 87 | 
88 | 89 |
 90 | {
 91 |     "STORAGE-ACCESS-INTRO": {
 92 |         "authors": ["John Wilander"],
 93 |         "date": "February 2018",
 94 |         "href": "https://webkit.org/blog/8124/introducing-storage-access-api/",
 95 |         "publisher": "WebKit",
 96 |         "rawDate": "2018-02-21",
 97 |         "status": "Blog post",
 98 |         "title": "Introducing Storage Access API"
 99 |     }
100 | }
101 | 
102 | 103 | 112 | 113 |
114 |

Introduction

115 | 116 | This section is non-normative. 117 | 118 | User Agents sometimes prevent content inside certain <{iframe}>s 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. 119 | 120 | The Storage Access API enables content inside <{iframe}>s 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]] 121 | 122 |
123 | 124 |

Infrastructure

125 | 126 | This specification depends on the Infra standard. [[!INFRA]] 127 | 128 |

The Storage Access API

129 | 130 | This specification defines a method to query whether or not a {{Document}} currently has access to its [=unpartitioned data=] ({{Document/hasStorageAccess()}}), and a method that can be used to request access to its [=unpartitioned data=] ({{Document/requestStorageAccess()}}). 131 | 132 |
133 | 134 | Alex visits `https://social.example/`. The page sets a cookie. This cookie has been set in a [=first-party-site context=]. 135 | 136 | 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|`.`{{Document/cookie}}, depending on User Agent storage access policies. 137 | 138 | Script in the <{iframe}> can call |doc|`.`{{Document/hasStorageAccess()}} to determine if it has access to the cookie. If it does not have access, it can request access by calling |doc|`.`{{Document/requestStorageAccess()}}. 139 | 140 |
141 | 142 | Unpartitioned data is client-side storage that would be available to a [=site=] were it loaded in a [=first-party-site context=]. 143 | 144 | 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 [=environment settings object/origin=] and [=top-level origin=] of its [=relevant settings object=] are [=/same site=] with one another. 145 | 146 | A {{Document}} is in a third party context if it is not in a [=first-party-site context=]. 147 | 148 | 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`". 149 | 150 | 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. 151 | 152 | 1. If the user agent does not have explicit settings for unpartitioned cookie access for |tuple|, return "`none`". 153 | 1. If the user agent's settings explicitly allow unpartitioned cookie access for |tuple|, return "`allow`". 154 | 1. [=Assert=]: the user agent's settings explicitly disallow unpartitioned cookie access for |tuple|. 155 | 1. Return "`disallow`". 156 | 157 | To determine the FedCM site connection status given a [=/origin=] |embedder| and [=/origin=] |identityProvider|, run the following steps. This algorithm returns a [=boolean=]. 158 | 1. [=list/iterate|For each=] |item| of [=connected accounts set=]: 159 | 1. Let (|rp|, |idp|, account) be |item|. 160 | 1. If |rp| and |embedder| are [=/same site=], and |idp| and |identityProvider| are [=/same site=], return true. 161 | 1. Return false. 162 | 163 | To determine the effective FedCM connection status given a [=/origin=] |embedder|, a [=/origin=] |identityProvider|, a {{Document}} |doc|, run the following steps. This algorithm returns a [=boolean=]. 164 | 1. Let |policyStatus| be whether |doc| is [=allowed to use=] "`identity-credentials-get`". 165 | 1. If |policyStatus| is "Disabled", return false. 166 | 1. Let |connected| be the result of [=determine the FedCM site connection status|determining the site connection status=] given |embedder| and |identityProvider|. 167 | 1. If |connected| is false, return false. 168 | 1. Let |preventSilentAccess| be [=user agent=]'s [=credential store=]'s [=prevent silent access flag=] for |embedder|. 169 | 1. If |preventSilentAccess|, return false. 170 | 1. Return true. 171 | 172 |

Changes to user agent state related to storage access

173 | 174 | Modify the definition of [=environment=] in the following manner: 175 | 1. Add a new member called has storage access of type [=boolean=]. 176 | 177 | Modify the definition of [=source snapshot params=] in the following manner: 178 | 1. Add a new member called has storage access of type [=boolean=]. 179 | 1. Add a new member called environment id of type opaque [=string=]. 180 | 181 | A storage access eligibility is one of "unset", "ineligible", or "eligible". 182 | 183 | A [=request=] has a [=storage access eligibility=] eligible for storage-access. It is initially "[=storage access eligibility/unset=]". 184 | 185 | Note: a [=request=]'s [=storage access eligibility=] indicates whether previously-granted "storage-access" permissions ought to be considered when evaluating which cookies to include on the [=request=]. In particular, note that after {{Document/requestStorageAccess}} has resolved and the [=environment=]'s [=environment/has storage access=] is set to true, not all of the [=request=]s issued by that [=environment=] ought to carry unpartitioned cookies. 186 |
187 |
188 | For example, suppose the user is visiting a page on https://top.com which embeds an <{iframe}> served from https://embed.com, and a script in that iframe has called {{Document/requestStorageAccess}} and the promise resolved. If the iframe subsequently fetches a resource from https://3p.com, that request will not include cookies via the Storage Access API. 189 | 190 |
191 | To determine the initial storage-access eligibility, given a [=request=] |request|, run the following steps: 192 | 193 | 1. If |request|'s [=request/client=] is null, return "[=storage access eligibility/unset=]". 194 | 1. If |request|'s [=request/client=]'s [=environment/ancestry=] is not "cross-site", return "[=storage access eligibility/unset=]" 195 | 196 | ISSUE: The concept of "ancestry" is being added to HTML in https://github.com/whatwg/html/pull/11133 197 | 198 | 1. If |request|'s [=request/client=]'s [=environment/has storage access=] is false, return "[=storage access eligibility/ineligible=]". 199 | 1. If |request|'s [=url/origin=] is not [=/same origin=] with |request|'s [=request/url=]'s [=url/origin=], return "[=storage access eligibility/ineligible=]". 200 | 1. Let |allowed| be the result of running [$Should request be allowed to use feature?$] given "storage-access" and |request|. 201 | 1. If |allowed| is false, return "[=storage access eligibility/ineligible=]". 202 | 1. Return "[=storage access eligibility/eligible=]". 203 | 204 |
205 | 206 |

Changes to {{Document}}

207 | 208 |
209 | partial interface Document {
210 |   Promise<boolean> hasStorageAccess();
211 |   Promise<undefined> requestStorageAccess();
212 | };
213 | 
214 | 215 | When invoked on {{Document}} |doc|, the hasStorageAccess() method must run these steps: 216 | 217 | 218 | 219 | 1. Let |p| be [=a new promise=]. 220 | 1. If |doc| is not [=Document/fully active=], then [=/reject=] |p| with an "{{InvalidStateError}}" {{DOMException}} and return |p|. 221 | 1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=/resolve=] |p| with false and return |p|. 222 | 1. Let |global| be |doc|'s [=relevant global object=]. 223 | 1. If |global| is not a [=secure context=], then [=/resolve=] |p| with false and return |p|. 224 | 1. If the [=top-level origin=] of |doc|'s [=relevant settings object=] is an [=opaque origin=], [=/resolve=] |p| with false and return |p|. 225 | 1. Let |browsingContext| be |doc|'s [=Document/browsing context=]. 226 | 1. Let |topLevelSite| be the result of [=obtain a site|obtaining a site=] from the [=top-level origin=] of |doc|'s [=relevant settings object=]. 227 | 1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |doc|'s [=Document/origin=]. 228 | 1. Run the following steps [=in parallel=]: 229 | 1. Let |explicitSetting| be the result of [=determine whether the user agent explicitly allows unpartitioned cookie access|determining whether the user agent explicitly allows unpartitioned cookie access=] with (|topLevelSite|, |embeddedSite|). 230 | 1. Let |permissionState| be the result of [=getting the current permission state=] given "storage-access" and |global|. 231 | 1. [=Queue a global task=] on the [=networking task source=] given |global| to: 232 | 1. If |explicitSetting| is "`disallow`", [=/resolve=] |p| with false. 233 | 1. If |explicitSetting| is "`allow`", [=/resolve=] |p| with true. 234 | 1. [=Assert=]: |explicitSetting| is "`none`". 235 | 1. If |browsingContext| is a [=top-level browsing context=], [=/resolve=] |p| with true. 236 | 1. If |browsingContext| is same authority with |browsingContext|'s [=top-level browsing context=]'s [=active document=], [=/resolve=] |p| with true. 237 | 238 | ISSUE: "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](https://github.com/whatwg/storage/issues/142#issuecomment-1122147159). In practice, this might involve comparing the [=site for cookies=] or performing a [=/same site=] check with the top-level document. 239 | 240 | 1. If |permissionState| is [=permission/granted=], [=/resolve=] |p| with |global|'s [=environment/has storage access=]. 241 | 242 | Note: The global storage access permission state takes precedence over the local [=environment/has storage access=] flag here, in order to immediately reflect a possible user choice to revoke the permission in their settings. 243 | 244 | 1. [=/Resolve=] |p| with false. 245 | 1. Return |p|. 246 | 247 | When invoked on {{Document}} |doc|, the requestStorageAccess() method must run these steps: 248 | 249 | 250 | 251 | 1. Let |p| be [=a new promise=]. 252 | 1. If |doc| is not [=Document/fully active=], then [=/reject=] |p| with an "{{InvalidStateError}}" {{DOMException}} and return |p|. 253 | 1. Let |global| be |doc|'s [=relevant global object=]. 254 | 1. Let |settings| be |doc|'s [=relevant settings object=]. 255 | 1. If |global| is not a [=secure context=], then [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|. 256 | 1. If |doc| is not [=allowed to use=] "`storage-access`", [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|. 257 | 1. If |doc|'s [=Document/origin=] is an [=opaque origin=], [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|. 258 | 1. If |settings|'s [=top-level origin=] is an [=opaque origin=], [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and return |p|. 259 | 1. 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|. 260 | 1. Let |browsingContext| be |doc|'s [=Document/browsing context=]. 261 | 1. Let |topLevelOrigin| be the [=top-level origin=] of |doc|'s [=relevant settings object=]. 262 | 1. Let |topLevelSite| be the result of [=obtain a site|obtaining a site=] from |topLevelOrigin|. 263 | 1. Let |embeddedOrigin| be |doc|'s [=Document/origin=]. 264 | 1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |embeddedOrigin|. 265 | 1. Let |has transient activation| be whether |doc|'s {{Window}} object has [=transient activation=]. 266 | 1. Run the following steps [=in parallel=]: 267 | 1. Let |process permission state| be an algorithm that, given a [=permission state=] |state|, runs the following steps: 268 | 1. [=Queue a global task=] on the [=networking task source=] given |global| to: 269 | 1. If |state| is [=permission/granted=]: 270 | 1. Set |global|'s [=environment/has storage access=] to true. 271 | 1. [=/Resolve=] |p| with {{undefined}}. 272 | 1. Else: 273 | 1. [=Consume user activation=] given |global|. 274 | 1. [=/Reject=] |p| with a "{{NotAllowedError}}" {{DOMException}}. 275 | 1. Let |explicitSetting| be the result of [=determine whether the user agent explicitly allows unpartitioned cookie access|determining whether the user agent explicitly allows unpartitioned cookie access=] with (|topLevelSite|, |embeddedSite|). 276 | 1. If |explicitSetting| is "`disallow`": 277 | 1. Run |process permission state| with [=permission/denied=]. 278 | 1. Abort these steps. 279 | 1. If |explicitSetting| is "`allow`": 280 | 1. Run |process permission state| with [=permission/granted=]. 281 | 1. Abort these steps. 282 | 1. [=Assert=]: |explicitSetting| is "`none`". 283 | 1. If |browsingContext| is a [=top-level browsing context=]: 284 | 1. Run |process permission state| with [=permission/granted=]. 285 | 1. Abort these steps. 286 | 1. If |embeddedSite| is [=site/same site=] with |topLevelSite|: 287 | 288 | NOTE: This check is [=site/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. 289 | 290 | 1. Run |process permission state| with [=permission/granted=]. 291 | 1. Abort these steps. 292 | 1. Let |previous permission state| be the result of [=getting the current permission state=] given "storage-access" and |global|. 293 | 1. If |previous permission state| is not [=permission/prompt=]: 294 | 1. Run |process permission state| with |previous permission state|. 295 | 1. Abort these steps. 296 | 1. Let |connected| be the result of [=Determine the effective FedCM connection status|determining the effective FedCM connection status=] given |topLevelOrigin|, |embeddedOrigin|, |doc|. 297 | 1. If |connected|: 298 | 299 | NOTE: User agents are encouraged to keep track of which (site, site) tuples have been allowed to access storage due to existing FedCM connections, and double-check that list when accessing cookies to catch malicious attackers that have tricked an [=environment=] into using an incorrect [=environment/has storage access=] bit. 300 | 301 | 1. Run |process permission state| with [=permission/granted=]. 302 | 1. Abort these steps. 303 | 1. If |has transient activation| is false: 304 | 1. Run |process permission state| with [=permission/denied=]. 305 | 1. Abort these steps. 306 | 1. Let |permissionState| be the result of [=requesting permission to use=] "storage-access". 307 | 308 | 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. 309 | 310 | 1. Run |process permission state| with |permissionState|. 311 | 1. Return |p|. 312 | 313 | 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. 314 | 315 | 316 | 317 | When [=snapshotting source snapshot params=]: 318 | 1. Set [=source snapshot params/has storage access=] to |sourceDocument|'s [=source snapshot params/has storage access=]. 319 | 1. Set [=source snapshot params/environment id=] to |sourceDocument|'s [=relevant settings object=]'s [=environment/id=]. 320 | 321 | To the [=create navigation params by fetching=] algorithm, insert the following step as step 3: 322 | 1. Let |originalURL| be entry's URL. 323 | 324 | When creating |request|'s [=reserved client=] in [=create navigation params by fetching=]: 325 | 1. Set [=reserved client=]'s [=environment/has storage access=] to |sourceSnapshotParams|'s [=source snapshot params/has storage access=] if all of the following hold: 326 | 1. |sourceSnapshotParams|'s [=source snapshot params/environment id=] equals navigable's [=active document=]'s [=relevant settings object=]'s [=environment/id=]. 327 | 1. |originalURL|'s [=url/origin=] is [=same origin=] with currentURL's [=url/origin=]. 328 | 1. |response| is null or |response|'s [=response/has-cross-origin-redirects=] is false. 329 | 1. Otherwise, set |request|'s [=reserved client=]'s [=environment/has storage access=] to false. 330 | 331 | When [=set up a window environment settings object|setting up a window environment settings object=]: 332 | 1. Set settings object's [=environment/has storage access=] to reserved environment's [=environment/has storage access=]. 333 | 334 |

Integration with Fetch

335 | 336 |

Fetching

337 | 338 | Insert a new step after step 14 of [=fetch=]: 339 | 340 |
341 | 15. Set |request|'s [=request/eligible for storage-access=] to the result of [=determining the initial storage-access eligibility=] given |request|. 342 |
343 | 344 |

HTTP-redirect-fetch

345 | 346 | Insert a new step after step 17 of [=HTTP-redirect fetch=]: 347 | 348 |
349 | 18. If |request|'s [=request/eligible for storage-access=] is not "[=storage access eligibility/unset=]" and locationURL's [=url/origin=] is not [=/same origin=] with |request|'s [=request/current URL=]'s [=url/origin=], set |request|'s [=request/eligible for storage-access=] to "[=storage access eligibility/ineligible=]". 350 |
351 | 352 |

Changes to various client-side storage mechanisms

353 | 354 | This API only impacts HTTP cookies. A future revision of this API might impact other client-side state. [[!RFC6265]] 355 | 356 |

Cookies

357 | 358 | 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 {{Document/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 [=request=]'s [=request/eligible for storage-access=] that is defined in this specification. 359 | 360 | Once the cookie store allows for receiving information about storage access, we would update [=HTTP-network-or-cache fetch=] and {{Document/cookie}} to pass the [=request=]'s [=request/eligible for storage-access=] to the [=cookie store=] when retrieving cookies. 361 | 362 | 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=]). 363 | 364 | 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. 365 | 366 |

Sandboxing storage access

367 | 368 | A [=/sandboxing flag set=] has a sandbox storage access by user activation flag. This flag prevents content from requesting storage access. 369 | 370 | To the [=parse a sandboxing directive=] algorithm, add the following under step 3: 371 | 372 | 375 | 376 |

Permissions Integration

377 | 378 | The Storage Access API defines a [=powerful feature=] identified by the [=powerful feature/name=] "storage-access". It defines the following permission-related algorithms: 379 | 380 |
381 |
[=powerful feature/permission query algorithm=]
382 |
383 | To query the "storage-access" permission, given a {{PermissionDescriptor}} |permissionDesc| and a {{PermissionStatus}} |status|: 384 | 385 | 1. Set |status|'s {{PermissionStatus/state}} to |permissionDesc|'s [=permission state=]. 386 | 1. If |status|'s {{PermissionStatus/state}} is [=permission/denied=], set |status|'s {{PermissionStatus/state}} to [=permission/prompt=]. 387 | 388 | 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. 389 |
390 |
[=powerful feature/permission key type=]
391 |
392 | A [=permission key=] of the "storage-access" feature is a [=tuple=] consisting of a [=site=] top-level and a [=site=] requester. 393 | 394 |
395 | 396 | `(("https", "news.example"), ("https", "social.example"))` is a [=permission key=] for "storage-access" whose [=permission key/top-level=] is `("https", "news.example")` and whose [=permission key/requester=] is `("https", "social.example")`. 397 | 398 |
399 |
400 |
[=powerful feature/permission key generation algorithm=]
401 |
402 | To generate a new [=permission key=] for the "storage-access" feature, given an [=/origin=] |origin| and [=/origin=] |embeddedOrigin|: 403 | 404 | 1. Let |topLevelSite| be the result of [=obtain a site|obtaining a site=] from |origin|. 405 | 1. Let |embeddedSite| be the result of [=obtain a site|obtaining a site=] from |embeddedOrigin|. 406 | 1. Return (|topLevelSite|, |embeddedSite|). 407 |
408 |
[=powerful feature/permission key comparison algorithm=]
409 |
410 | To compare the [=permission keys=] |key1| and |key2| for the "storage-access" feature, run the following steps: 411 | 412 | 1. If |key1|'s [=permission key/top-level=] is not [=site/same site=] with |key2|'s [=permission key/top-level=], return false. 413 | 1. If |key1|'s [=permission key/requester=] is not [=site/same site=] with |key2|'s [=permission key/requester=], return false. 414 | 1. Return true. 415 |
416 |
417 | 418 |

Permissions Policy Integration

419 | 420 | The Storage Access API defines a [=policy-controlled feature=] identified by the string `"storage-access"`. Its [=default allowlist=] is `"*"`. 421 | 422 | Note: A {{Document}}’s [=Document/permissions policy=] determines whether any content in that document is allowed to request storage access using {{Document/requestStorageAccess()}}. If disabled in any document, calling {{Document/requestStorageAccess()}} in that document will reject. 423 | 424 |

Privacy considerations

425 | 426 | 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. 427 | 428 | A nested {{Document}} gains access to the same cookies it has as the [=active document=] of a [=top-level browsing context=] when it calls {{Document/requestStorageAccess()}} and is returned a resolving {{Promise}}. With these cookies it can authenticate itself to the server and load user-specific information. 429 | 430 | 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. 431 | 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 [=site|sites=] of the embedding and embedded {{Document}}. 432 | 433 | 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=]. 434 | 435 | In an ideal case, a nested {{Document}} would only be able to gain access to its [=unpartitioned data=] if: 436 | 437 | 1. the user interacts with the nested {{Document}} 438 | 2. the nested {{Document}} is permitted by the embedder to use the API 439 | 3. the nested {{Document}} is a [=secure context=] 440 | 4. the user grants express, pairwise permission to the embeddee to use its cookies in the embedder 441 | 5. the user is not inundated by requests for the "storage-access" permission so their express permission is not undermined by fatigue 442 | 443 | 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. 444 | 445 | The last two points are in tension. In an ideal world, we would show a prompt to the user in every call to {{Document/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. 446 | 447 |
448 | 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”. 450 |
An example prompt which could be shown to the user when a site calls `document.`{{Document/requestStorageAccess()}}.
451 |
452 | 453 | 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. 454 | 455 | 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. 456 | 457 |

Permission scope

458 | 459 | Another tension in the design of the API is what to use to key the "storage-access" permission: [=/origin|origins=] or [=site|sites=]. We chose [=site|sites=] because we believe them to be acceptable boundaries for privacy while enabling existing uses of [=site/same site=] and cross-origin nested {{Document|Documents}} on the same page with only one user prompt. 460 | 461 |

Security considerations

462 | 463 | 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. 464 | 465 | 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 {{Document/requestStorageAccess()}} and only until the nested {{Document}} navigates across an [=/origin=] boundary. This ensures that only [=/origin|origins=] with a page that call {{Document/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. 466 | 467 |

Reputational attacks

468 | 469 | 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. 470 | 471 | The embedder has control over which nested {{Document|Documents}} have the ability to become authenticated, or even display a permission request to the user via the Permission Policy and nested {{Document}} sandboxing. 472 | 473 |

Notification abuse

474 | 475 | 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. 476 | 477 |

Automation

478 | 479 | For the purposes of user-agent automation and application testing, this document defines the following [=extension command=] for the [[WebDriver]] specification. 480 | 481 |

Set Storage Access

482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 |
HTTP MethodURI Template
POST/session/{session id}/storageaccess
495 | 496 | The Set Storage Access [=extension command=] modifies the storage access policy for the [=current browsing context=]. 497 | 498 | The [=remote end steps=] are: 499 | 500 | 1. Let |blocked| be the result of [=getting a property=] from |parameters| named `blocked`. 501 | 1. If |blocked| is not a [=boolean=] return a [=WebDriver error=] with [=WebDriver error code=] [=invalid argument=]. 502 | 1. Let |embedded origin| be the result of [=getting a property=] from |parameters| named `origin`. 503 | 1. If |embedded origin| is not a single U+002A ASTERISK character (*), then: 504 | 1. Let |parsedURL| be the the result of running the [=URL parser=] on |embedded origin|. 505 | 1. If |parsedURL| is failure, then return a [=WebDriver error=] with [=WebDriver error code=] [=invalid argument=]. 506 | 1. Set |embedded origin| to |parsedURL|'s [=url/origin=]. 507 | 1. If the [=current browsing context=] is not a [=top-level browsing context=] return a [=WebDriver error=] with [=WebDriver error code=] [=unsupported operation=]. 508 | 1. Let |doc| be the [=current browsing context=]'s [=active document=]. 509 | 1. Let |settings| be |doc|'s [=relevant settings object=]. 510 | 1. Let |top-level site| be the result of [=obtain a site|obtaining a site=] from |settings|'s [=environment settings object/origin=]. 511 | 1. If |embedded origin| is a single U+002A ASTERISK character (*), then: 512 | 1. If |blocked| is `true`, then: 513 | 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|. 514 | 1. Otherwise, if |blocked| is `false`, then: 515 | 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|. 516 | 1. Otherwise: 517 | 1. If |embedded origin| is [=/same site=] with |settings|'s [=environment settings object/origin=] return a [=WebDriver error=] with [=WebDriver error code=] [=unsupported operation=]. 518 | 1. If |blocked| is `true`, then: 519 | 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|. 520 | 1. Otherwise, if |blocked| is `false`, then: 521 | 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|. 522 | 1. If the above [=implementation-defined=] step of steps resulted in failure, return a [=WebDriver error=] with [=WebDriver error code=] [=unknown error=]. 523 | 1. Return [=success=] with data `null`. 524 | 525 |

Acknowledgements

526 | 527 | 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. 528 | 529 | Many thanks to 530 | Anne van Kesteren, 531 | Ben Kelly, 532 | Brad Girardeau, 533 | Brad Hill, 534 | Brady Eidson, 535 | Brandon Maslen, 536 | Chris Mills, 537 | Dave Longley, 538 | Domenic Denicola, 539 | Ehsan Akhgari, 540 | Geoffrey Garen, 541 | Jack Frankland, 542 | James Coleman, 543 | James Hartig, 544 | Jeffrey Yasskin, 545 | Kushal Dave, 546 | Luís Rudge, 547 | Maciej Stachowiak, 548 | Matias Woloski, 549 | Mike O'Neill, 550 | Mike West, 551 | Pete Snyder, 552 | Rob Stone, 553 | Stefan Leyhane, 554 | Steven Englehardt, 555 | Travis Leithead, 556 | Yan Zhu, 557 | Zach Edwards, 558 | and everyone who commented on [whatwg/html#3338](https://github.com/whatwg/html/issues/3338), [privacycg/proposals#2](https://github.com/privacycg/proposals/issues/2), and [privacycg/storage-access/issues](https://github.com/privacycg/storage-access/issues) 559 | for their feedback on this proposal. 560 | 561 | Thanks to the [WebKit Open Source Project](https://webkit.org/) for allowing us to use the [Storage Access API Prompt](#example-prompt) image, which was [originally published on webkit.org](https://webkit.org/blog/8311/intelligent-tracking-prevention-2-0/). 562 | --------------------------------------------------------------------------------