From f2198bd0f5204855101ccb1afbdff357ab5059f1 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 3 May 2023 15:03:18 +0100 Subject: [PATCH 01/11] Update Storage Access API content --- .../api/document/hasstorageaccess/index.md | 7 +- files/en-us/web/api/document/index.md | 2 +- .../document/requeststorageaccess/index.md | 29 +++--- .../en-us/web/api/storage_access_api/index.md | 89 ++++++++++++------- .../web/api/storage_access_api/using/index.md | 13 +-- files/en-us/web/html/element/iframe/index.md | 4 +- .../http/headers/permissions-policy/index.md | 4 + .../storage-access/index.md | 43 +++++++++ 8 files changed, 139 insertions(+), 52 deletions(-) create mode 100644 files/en-us/web/http/headers/permissions-policy/storage-access/index.md diff --git a/files/en-us/web/api/document/hasstorageaccess/index.md b/files/en-us/web/api/document/hasstorageaccess/index.md index 57b15f9da0cdbb8..e5c2a52cb576421 100644 --- a/files/en-us/web/api/document/hasstorageaccess/index.md +++ b/files/en-us/web/api/document/hasstorageaccess/index.md @@ -8,7 +8,7 @@ browser-compat: api.Document.hasStorageAccess {{APIRef("Storage Access API")}} -The **`hasStorageAccess()`** method of the {{domxref("Document")}} interface returns a {{jsxref("Promise")}} that resolves with a boolean value indicating whether the document has access to its first-party storage. +The **`hasStorageAccess()`** method of the {{domxref("Document")}} interface returns a {{jsxref("Promise")}} that resolves with a boolean value indicating whether the document has access to its first-party client-side storage. This method is part of the [Storage Access API](/en-US/docs/Web/API/Storage_Access_API). @@ -28,6 +28,11 @@ A {{jsxref("Promise")}} that resolves with a boolean value indicating whether th If the promise gets resolved and a user gesture event was being processed when the function was originally called, the resolve handler will run as if a user gesture was being processed, so it will be able to call APIs that require user activation. +### Exceptions + +- `InvalidStateError` {{domxref("DOMException")}} + - : Thrown if the current {{domxref("Document")}} is not yet active. + ## Examples ```js diff --git a/files/en-us/web/api/document/index.md b/files/en-us/web/api/document/index.md index 0bdb3d029d7efe5..fb81a611e123889 100644 --- a/files/en-us/web/api/document/index.md +++ b/files/en-us/web/api/document/index.md @@ -257,7 +257,7 @@ _This interface also inherits from the {{DOMxRef("Node")}} and {{DOMxRef("EventT - {{DOMxRef("Document.replaceChildren()")}} - : Replaces the existing children of a document with a specified new set of children. - {{DOMxRef("Document.requestStorageAccess()")}} - - : Returns a {{jsxref("Promise")}} that resolves if the access to first-party storage was granted, and rejects if access was denied. + - : Allows a document loaded in a third-party context (i.e. embedded in an {{htmlelement("iframe")}}) to request access to its client-side storage, in cases where user agents by default block access to client-side storage by sites loaded in a third-party context to improve privacy. - {{domxref("Document.startViewTransition()")}} {{Experimental_Inline}} - : Starts a new {{domxref("View Transitions API", "view transition", "", "nocode")}} and returns a {{domxref("ViewTransition")}} object to represent it. - {{DOMxRef("Document.mozSetImageElement()")}} {{Non-standard_Inline}} diff --git a/files/en-us/web/api/document/requeststorageaccess/index.md b/files/en-us/web/api/document/requeststorageaccess/index.md index 7f13972b8fb79f2..3fecdcc7692e4d7 100644 --- a/files/en-us/web/api/document/requeststorageaccess/index.md +++ b/files/en-us/web/api/document/requeststorageaccess/index.md @@ -6,11 +6,13 @@ page-type: web-api-instance-method browser-compat: api.Document.requestStorageAccess --- -{{APIRef("Storage Access API")}} +{{APIRef("DOM")}} -The **`requestStorageAccess()`** method of the {{domxref("Document")}} interface returns a {{jsxref("Promise")}} that resolves if the access to first-party storage was granted, and rejects if access was denied. +The **`requestStorageAccess()`** method of the {{domxref("Document")}} interface allows a document loaded in a third-party context (i.e. embedded in an {{htmlelement("iframe")}}) to request access to its client-side storage. -This is part of the [Storage Access API](/en-US/docs/Web/API/Storage_Access_API). +This is relevant to user agents that by default block access to client-side storage by sites loaded in a third-party context to improve privacy (e.g. to prevent tracking), and is part of the [Storage Access API](/en-US/docs/Web/API/Storage_Access_API). + +> **Note:** Usage of this feature may be blocked by a {{httpheader("Permissions-Policy/storage-access", "storage-access")}} [Permissions Policy](/en-US/docs/Web/HTTP/Permissions_Policy) set on your server. In addition, the document must pass additional browser-specific checks such as allowlists, blocklists, on-device classification, user settings, anti-[clickjacking](/en-US/docs/Glossary/Clickjacking) heuristics, or prompting the user for explicit permission. ## Syntax @@ -31,6 +33,17 @@ When the promise gets resolved, the resolve handler will run as if a user gestur - In the former case, code can then start to call APIs that require user activation and things can move forward. - In the latter case, code can run to inform the user of why the request failed and what they can do to continue (for example asking them to log in, if that is a requirement). +### Exceptions + +- `InvalidStateError` {{domxref("DOMException")}} + - : Thrown if the current {{domxref("Document")}} is not yet active. +- `NotAllowedError` {{domxref("DOMException")}} + - : Thrown if: + - The document's window is not a [secure context](/en-US/docs/Web/Security/Secure_Contexts). + - Usage is blocked by a {{httpheader("Permissions-Policy/storage-access", "storage-access")}} [Permissions Policy](/en-US/docs/Web/HTTP/Permissions_Policy). + - The document or the top-level document has a `null` origin. + - The embedding {{htmlelement("iframe")}} is sandboxed, and the `allow-storage-access-by-user-activation` token is not set. + ## Examples ```js @@ -44,16 +57,6 @@ document.requestStorageAccess().then( ); ``` -## Security - -Access to cross-site cookies is granted to iframes based on a number of prerequisites: - -1. The browser must be processing a user gesture ({{Glossary("transient activation")}}). -2. The document or the top-level document must not have a null origin. -3. The document's window must be a [secure context](/en-US/docs/Web/Security/Secure_Contexts). -4. If the document is sandboxed, it must have the `allow-storage-access-by-user-activation` token. -5. The document must pass additional browser-specific checks. Examples: allowlists, blocklists, on-device classification, user settings, anti-[clickjacking](/en-US/docs/Glossary/Clickjacking) heuristics, or prompting the user for explicit permission. - ## Specifications {{Specifications}} diff --git a/files/en-us/web/api/storage_access_api/index.md b/files/en-us/web/api/storage_access_api/index.md index c8b7d84ef5ad811..f9cccae3f745438 100644 --- a/files/en-us/web/api/storage_access_api/index.md +++ b/files/en-us/web/api/storage_access_api/index.md @@ -9,56 +9,89 @@ browser-compat: {{DefaultAPISidebar("Storage Access API")}} -The Storage Access API provides a way for embedded, cross-origin content to gain unrestricted access to storage that it would normally only have access to in a first-party context (we refer to this as an origin's _first-party_ storage). +The Storage Access API provides a way for cross-origin content loaded in a third-party context (i.e. embedded in an {{htmlelement("iframe")}}) to gain access to client-side storage that it would normally only have access to in a first-party context (i.e. when loaded directly in a browser tab; we refer to this as an origin's _first-party_ storage). -The API provides methods that allow embedded resources to check whether they currently have access to their first-party storage, and to request access to their first-party storage from the user agent. +This is relevant to user agents that by default block access to client-side storage by sites loaded in a third-party context to improve privacy (e.g. to prevent tracking). There are legitimate uses of first-party client-side storage access by third-party content that we still want to enable, even with these default restrictions in place. Examples include SSO with federated IdPs, or persisting user details across different sites such as location data or viewing preferences. + +The API provides methods that allow embedded resources to check whether they currently have access to their first-party client-side storage, and to request access to their first-party storage from the user agent. + +> **Note:** Bear in mind that when we say "first-party client-side storage" in this documentation, we are basically talking about access to cookies. We have however written it with a more generic focus, as it may be useful for other forms of client-side storage in the future. ## Concepts and usage -Most browsers implement a number of storage access policies that restrict access to cookies and site data for embedded, cross-origin resources. These restrictions range from giving embedded resources under each top-level origin a unique storage space to outright blocking of storage access when resources are loaded in a third-party context. +Most browsers implement a number of storage access features and policies that restrict access to cookies and site data for embedded, cross-origin resources. These range from giving embedded resources under each top-level origin a unique storage space (see for example [Cookies Having Independent Partitioned State (CHIPS)](/en-US/docs/Web/Privacy/Partitioned_cookies)) to outright blocking of storage access when resources are loaded in a third-party context. + +The semantics around third-party cookie blocking features/policies differ from browser to browser, but the core functionality is similar: cross-origin resources embedded in a third-party context are not given access to the same cookies and site storage that they would have access to when loaded in a first-party context. This is done with good intent — browser vendors want to take steps to better protect their user's privacy and security, for example leaving them less open to having their activity tracked across different sites, and less vulnerable to exploits such as cross-site request forgery ({{glossary("CSRF")}}). + +However, there are legitimate uses for embedded cross-origin content accessing its first-party storage, which the above features/policies are known to break. As an example, federated logins often require access to authentication cookies stored in first-party storage, and will require the user to sign in on each site separately (or completely break) if those cookies are not available. As a result, site owners often encourage users to add their site as an exception or to disable such policies entirely. Users who wish to continue to interact with embedded content are forced to greatly relax their blocking policy for resources loaded from all embedded origins and possibly across all websites. + +The Storage Access API is intended to solve this problem; embedded cross-origin content can request unrestricted access to its first-party storage on a frame-by-frame basis, for a particular top-level embedding site, via the {{domxref("Document.requestStorageAccess()")}} method. It can also check whether it already has access via the {{domxref("Document.hasStorageAccess()")}} method. + +## How it works -The semantics around third-party cookie blocking policies in particular differ from browser to browser, but the core functionality is similar: cross-origin resources embedded in a third-party context are not given access to the same cookies and site storage that they would have access to when loaded in a first-party context. +As explained above, modern browsers implement various features and policies to restrict or block access to first-party storage by embedded content. Such content that has a legitimate need for first-party storage access can get around this problem as follows: -These cookie blocking policies are known to break embedded cross-origin content that requires access to its first-party storage. As an example, federated logins often require access to authentication cookies stored in first-party storage, and will require the user to sign in on each site separately (or completely break) if those cookies are not available. In the case of breakage, site owners have often encouraged users to add their site as an exception or to disable the policy entirely. As a consequence, users who wish to continue to interact with embedded content are forced to greatly relax their blocking policy for resources loaded from all embedded origins and possibly across all websites. +1. It can call the {{domxref("Document.hasStorageAccess()")}} method to check whether it has the access it needs already. +2. If not, it can request access via the {{domxref("Document.requestStorageAccess()")}} method. +3. Depending on the browser, the user will be asked whether to grant storage access to the requesting embed in slightly differing ways. + - Safari shows prompts for all embedded tracking content that has not previously received storage access. + - Firefox only prompts users after a tracking origin has requested storage access on more than a threshold number of sites. + - At the time of writing, Chrome doesn't prompt the user. Instead, it only resolves requestStorageAccess() calls that come from domains within a first-party set. This behavior will change as the implementation is developed further. +4. Access is granted or denied based on whether the content meets all the security requirements (see [Security measures](#security_measures), below). The {{jsxref("Promise")}}-based nature of `requestStorageAccess()` allows you to run code to handle success and failure cases. + - Modern spec behavior dictates that access is granted **per-frame** — every separate content embed has its access blocked by default, and needs to call `requestStorageAccess()` to opt in to access. If a content embed has received access, and same-site embeds then call `requestStorageAccess()`, their promises will fulfill automatically. But they still need to opt in. + - In older versions of the spec, the access was **per-page**. As soon as one embed received storage access via `requestStorageAccess()`, all other same-site embeds would automatically receive storage access. This was not desirable behavior from a security standpoint — if `shop.example.com` embedded `locator.users.com` to allow users to use their location info while shopping, and `locator.users.com` called `requestStorageAccess()`, `shop.example.com` and any other sites it embeds would be able to access its cookies, but also access cookies from `private.users.com`, which is not intended to be embedded. [Read more about the motivations](https://github.com/privacycg/storage-access/issues/113) behind this change. + - Consult the [Browser compatibility information](#browser_compatibility) to see which browsers implement which behavior. +5. Once access is granted, a permission key is stored in the browser with the structure ``. For example, if the embedding site is `embedder.com`, and the embed is `locator.example.com`, the key would be ``. Same-site embeds (`docs.example.com`, `profile.example.com`, etc.) would then be able to call `requestStorageAccess()` and the promise would fulfill automatically, as mentioned earlier. + - Older versions of the spec used the more specific permission key structure ``, which meant that same-site embeds didn't match the permission key and had to go through the whole process separately. + - At the time of writing, only Chromium browsers had implemented the new behavior. -The Storage Access API is intended to solve this problem; embedded cross-origin content can request unrestricted access to its first-party storage on a site-by-site basis via the {{domxref("Document.requestStorageAccess()")}} method, and check whether it already has access via the {{domxref("Document.hasStorageAccess()")}} method. +> **Note:** See [Using the Storage Access API](/en-US/docs/Web/API/Storage_Access_API/Using) for an implementation guide, with code examples. -In addition, sandboxed {{htmlelement("iframe")}}s cannot be granted storage access by default for security reasons. The API therefore also adds the `allow-storage-access-by-user-activation` [sandbox token](/en-US/docs/Web/HTML/Element/iframe#sandbox). The embedding website needs to add this to allow storage access requests to be successful, along with `allow-scripts` and `allow-same-origin` to allow it to call the API, and execute in an origin that can have cookies: +> **Note:** The only exception to the "blocked by default" behavior is when a content embed makes a successful `requestStorageAccess()`, but then performs a same-origin navigation (for example reloading itself). In such cases, the storage access is carried over from the previous navigation. -```html - -``` +## Security measures -The API is designed to limit the potential storage exceptions to origins for which the user has shown an intent to interact. This is enforced through the following constraints: +There are a number of different related security measures that could cause a {{domxref("Document.requestStorageAccess()")}} call to fail. Check the below list if you are having trouble getting a request to work: -- Access requests are automatically denied unless the embedded content is currently processing a user gesture such as a tap or click. This also prevents embedded content on the page from spamming the browser or user with excessive access requests. -- Origins that have never been interacted with as a first party do not have a notion of first-party storage. From the user's perspective, they only have a third-party relationship with that origin. Access requests are automatically denied if the browser detects that the user hasn't interacted with the embedded content in a first-party context recently (in Firefox, "recently" is "within 30 days"). +1. The call must be associated with a user gesture ({{Glossary("transient activation")}}) such as a tap or click. This prevents embedded content on the page from spamming the browser or user with excessive access requests. +2. The document and top-level document must not have a `null` origin. +3. Origins that have never been interacted with as a first party do not have a notion of first-party storage. From the user's perspective, they only have a third-party relationship with that origin. Access requests are automatically denied if the browser detects that the user hasn't interacted with the embedded content in a first-party context recently (in Firefox, "recently" is "within 30 days"). +4. The document's window must be a [secure context](/en-US/docs/Web/Security/Secure_Contexts). +5. Sandboxed {{htmlelement("iframe")}}s cannot be granted storage access by default for security reasons. The API therefore also adds the `allow-storage-access-by-user-activation` [sandbox token](/en-US/docs/Web/HTML/Element/iframe#sandbox). The embedding website needs to add this to allow storage access requests to be successful, along with `allow-scripts` and `allow-same-origin` to allow it to call the API, and execute in an origin that can have cookies: -The browser may decide to involve the user in the decision of whether to grant an incoming storage access request. Specifics regarding the lifetime of a storage grant and the circumstances under which the browser may decide to inform the user are currently being worked through and will be announced once ready. + ```html + + ``` -## User prompts +6. Usage of this feature may be blocked by a {{httpheader("Permissions-Policy/storage-access", "storage-access")}} [Permissions Policy](/en-US/docs/Web/HTTP/Permissions_Policy) set on your server. +7. At the time of writing, Chrome only resolves requestStorageAccess() calls that come from domains within a first-party set. This restriction is intended to be relaxed as the implementation is developed further. -When `requestStorageAccess()` is called by an embedded, cross-origin document, the user agent may choose to involve the user in the decision of whether to grant storage access to the requesting origin. Prompting heuristics currently vary across the two implementers of the Storage Access API — Safari shows prompts for all embedded tracking content that has not previously received storage access, while Firefox only prompts users after a tracking origin has requested storage access on more than a threshold number of sites. See {{domxref("Document.requestStorageAccess()")}} for more details. +> **Note:** The document may also be required to pass additional browser-specific checks. Examples: allowlists, blocklists, on-device classification, user settings, anti-[clickjacking](/en-US/docs/Glossary/Clickjacking) heuristics, or prompting the user for explicit permission. -## Safari implementation differences +## Browser storage access policy variations -Although the API surface is the same, websites using the Storage Access API should expect differences in the level and extent of storage access they receive between Firefox and Safari. This is caused by differences in the storage access policies implemented in the two browsers. Design properties unique to Firefox are summarized here: +Although the API surface is the same, websites using the Storage Access API should expect differences in the level and extent of storage access they receive between different browsers, due to differences in their storage access policies. + +### Firefox + +Design properties unique to Firefox are summarized here: - If the embedded origin `tracker.example` has already obtained first-party storage access on the top-level origin `foo.example`, and the user visits a page from `foo.example` embedding a page from `tracker.example` again in less than 30 days, the embedded origin will have storage access immediately when loading. - If an embedded page from `tracker.example` has previously successfully obtained storage access on top-level origin `foo.example`, all embedded subresources from `tracker.example` on `foo.example` (e.g. scripts, images, stylesheets, etc.) will load with access to their first-party storage, which means they may send Cookie headers and honor incoming {{httpheader("Set-Cookie")}} headers. - In Firefox, when the promise returned from `requestStorageAccess()` is resolved, the embedded page will gain access to its entire first-party storage, not just cookies. This includes access to APIs such as [Web Storage](/en-US/docs/Web/API/Web_Storage_API), [IndexedDB](/en-US/docs/Web/API/IndexedDB_API), [DOM Cache](/en-US/docs/Web/API/Cache), and so on. -- In Firefox, the storage access grants are phased out after 30 calendar days passing, whereas in Safari the storage access grants are phased out after 30 days of browser usage passed without user interaction. This is currently a limitation of the Firefox implementation, which we may address in a future version. In Safari, successful use of the storage access API resets this counter. +- The storage access grants are phased out after 30 calendar days passing. Documentation for Firefox's new storage access policy for blocking tracking cookies includes [a detailed description](/en-US/docs/Web/Privacy/Storage_Access_Policy#storage_access_grants) of the scope of storage access grants. -## Storage Access API methods +### Safari + +- The storage access grants are phased out after 30 days of browser usage passed without user interaction. Successful use of the storage access API resets this counter. -The storage API methods are implemented on the {{domxref("Document")}} interface: +## API methods - {{domxref("Document.hasStorageAccess()")}} - : Returns a {{jsxref("Promise")}} that resolves with a boolean value indicating whether the document has access to its first-party storage. @@ -67,10 +100,6 @@ The storage API methods are implemented on the {{domxref("Document")}} interface > **Note:** User interaction propagates to the Promise returned by both of these methods, allowing the callers to take actions that require user interaction without requiring a second click from the user. For example, a caller could open a pop-up window from the resolved Promise without triggering Firefox's pop-up blocker. -## Extensions to \ ``` +## Checking and requesting storage access + Now on to the code executed inside the embedded document. Since it does not know whether it currently has access to storage, it should first call {{domxref("Document.hasStorageAccess()")}}. If that call returns `false`, we can then call {{domxref("Document.requestStorageAccess()")}}, returning the result so that then we can chain it onto the previous promise call. In the final `then`, we'll have first-party storage access. ```js @@ -63,7 +64,9 @@ if (document.hasStorageAccess == null) { } ``` -Note that access requests are automatically denied unless the embedded content is currently processing a user gesture such as a tap or click — so this code needs to be run inside some kind of user gesture-based event handler, for example: +## Transient activation required + +Note that access requests are automatically denied unless the embedded content is currently processing a user gesture such as a tap or click ({{Glossary("transient activation")}}). The above code therefore needs to be run inside some kind of user gesture-based event handler, for example: ```js btn.addEventListener("click", () => { diff --git a/files/en-us/web/html/element/iframe/index.md b/files/en-us/web/html/element/iframe/index.md index 2712c4e1fe91a81..dfc83179e67a5b0 100644 --- a/files/en-us/web/html/element/iframe/index.md +++ b/files/en-us/web/html/element/iframe/index.md @@ -71,7 +71,7 @@ This element includes the [global attributes](/en-US/docs/Web/HTML/Global_attrib - `sandbox` - - : Applies extra restrictions to the content in the frame. The value of the attribute can either be empty to apply all restrictions, or space-separated tokens to lift particular restrictions: + - : Controls the restrictions applied to the content embedded in the `