Extending Storage Access API (SAA) to non-cookie storage

Draft Community Group Report,

This version:
https://privacycg.github.io/saa-non-cookie-storage/
Issue Tracking:
GitHub
Inline In Spec
Editors:
(Google)
(Google)

Abstract

This extends the Storage Access API to enable content in cross-site iframes to request access to first-party data beyond cookies.

Status of this document

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

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

1. Introduction

This section is non-normative.

The Storage Access API (SAA) enables content inside iframes to request and be granted access to their client-side storage, so that embedded content which relies on having access to client-side storage can work in such User Agents. [STORAGE-ACCESS]

This specification extends the client-side storage available beyond cookies.

This specification defines a method to request access to unpartitioned data beyond just cookies (requestStorageAccess(types)), and a method to check if cookie access has specifically been granted (hasUnpartitionedCookieAccess()).

Alex visits https://social.example/. The page sets a some local storage. This local storage has been set in a first-party-site context.

window.localStorage.setItem("userid", "1234");

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 local storage set previously might or might not be visible depending on User Agent storage access policies.

Script in the iframe can call doc.requestStorageAccess(types) to request access.

let handle = await document.requestStorageAccess({localStorage: true});
let userid = handle.localStorage.getItem("userid");

2.1. Changes to Document

dictionary StorageAccessTypes {
  boolean all = false;
  boolean cookies = false;
  boolean sessionStorage = false;
  boolean localStorage = false;
  boolean indexedDB = false;
  boolean locks = false;
  boolean caches = false;
  boolean getDirectory = false;
  boolean estimate = false;
  boolean createObjectURL = false;
  boolean revokeObjectURL = false;
  boolean BroadcastChannel = false;
  boolean SharedWorker = false;
};

interface StorageAccessHandle {
  readonly attribute Storage sessionStorage;
  readonly attribute Storage localStorage;
  readonly attribute IDBFactory indexedDB;
  readonly attribute LockManager locks;
  readonly attribute CacheStorage caches;
  Promise<FileSystemDirectoryHandle> getDirectory();
  Promise<StorageEstimate> estimate();
  DOMString createObjectURL((Blob or MediaSource) obj);
  undefined revokeObjectURL(DOMString url);
  BroadcastChannel BroadcastChannel(DOMString name);
  SharedWorker SharedWorker(USVString scriptURL, optional (DOMString or SharedWorkerOptions) options = {});
};

partial interface Document {
  Promise<boolean> hasUnpartitionedCookieAccess();
  Promise<StorageAccessHandle> requestStorageAccess(StorageAccessTypes types);
};

A StorageAccessHandle object has an associated StorageAccessTypes types.

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

  1. Return the result of running hasStorageAccess() on doc.

Note: Now that requestStorageAccess(types) can be used to request unpartitioned data with or without specifically requesting cookies, it must be made clear that hasStorageAccess() only returns true if first-party-site context cookies are accessable to the current document. As a function name, hasUnpartitionedCookieAccess() more clearly communicates this. For now hasStorageAccess() is not considered deprecated, but that may be worth taking up in future.

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

  1. Let p be a new promise.

  2. If types.all is false and types.cookies is false and types.sessionStorage is false and types.localStorage is false and types.indexedDB is false and types.locks is false and types.caches is false and types.getDirectory is false and types.estimate is false and types.createObjectURL is false and types.revokeObjectURL is false and types.BroadcastChannel is false and types.SharedWorker is false:

    1. Reject p with an "InvalidStateError" DOMException.

    2. Return p.

  3. Let requestUnpartitionedCookieAccess be true if types.all is true or types.cookies is true, and false otherwise.

  4. Let accessPromise be the result of running request storage access with doc with requestUnpartitionedCookieAccess.

  5. If accessPromise rejects with reason r:

    1. Reject p with r.

  6. Else:

    1. Let handle be a new object of type StorageAccessHandle.

    2. Set handle’s types to types.

    3. Resolve p with handle.

  7. Return p.

2.2. Changes to requestStorageAccess()

Redefine requestStorageAccess() to:

  1. Return the result of running request storage access with doc and requestUnpartitionedCookieAccess being true.

Modify requestStorageAccess() to instead be the algorithm request storage access which takes a Document doc and a boolean argument requestUnpartitionedCookieAccess.

Modify requestStorageAccess() at step 14.1.1.1.1 to read:

  1. If requestUnpartitionedCookieAccess is true, then set global’s has storage access to true.

2.3. Changes to various client-side storage mechanisms

For all of the following getters and methods, consider the following modifications:

  1. When attempting to obtain a storage key for non-storage purposes the returned key will use Client-Side Storage Partitioning § relaxing-additional-keying if the tuple does not simply contain an origin.

Clarify client-side storage mechanism changes in more detail. [Issue #19]

2.3.1. Web storage

The sessionStorage getter steps are:

  1. If this’s types.all is false and this’s types.sessionStorage is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running sessionStorage.

The localStorage getter steps are:

  1. If this’s types.all is false and this’s types.localStorage is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running localStorage.

2.3.2. Indexed Database API

The indexedDB getter steps are:

  1. If this’s types.all is false and this’s types.indexedDB is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running indexedDB.

2.3.3. Web Locks API

The locks getter steps are:

  1. If this’s types.all is false and this’s types.locks is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running locks on Navigator.

2.3.4. Cache Storage

The caches getter steps are:

  1. If this’s types.all is false and this’s types.caches is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running caches.

2.3.5. File System

When invoked on StorageAccessHandle handle with StorageAccessTypes types, the getDirectory() method must run these steps:

  1. Let p be a new promise.

  2. If types.all is false and types.getDirectory is false:

    1. Reject p with an "InvalidStateError" DOMException.

  3. Let directoryPromise be the result of running getDirectory() on Navigator.storage.

  4. If directoryPromise rejects with reason r:

    1. Reject p with r.

  5. Else if directoryPromise resolves with FileSystemDirectoryHandle f:

    1. Resolve p with f.

  6. Return p.

2.3.6. Storage Manager

When invoked on StorageAccessHandle handle with StorageAccessTypes types, the estimate() method must run these steps:

  1. Let p be a new promise.

  2. If types.all is false and types.estimate is false:

    1. Reject p with an "InvalidStateError" DOMException.

  3. Let estimatePromise be the result of running estimate() on Navigator.storage.

  4. If estimatePromise rejects with reason r:

    1. Reject p with r.

  5. Else if estimatePromise resolves with StorageEstimate e:

    1. Resolve p with e.

  6. Return p.

2.3.7. File API

When invoked on StorageAccessHandle handle with StorageAccessTypes types and Blob or MediaSource obj, the createObjectURL(obj) method must run these steps:

  1. If types.all is false and types.createObjectURL is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running createObjectURL on URL with obj.

When invoked on StorageAccessHandle handle with StorageAccessTypes types and DOMString url, the revokeObjectURL(url) method must run these steps:

  1. If types.all is false and types.revokeObjectURL is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running revokeObjectURL on URL with url.

2.3.8. Broadcast Channel

When invoked on StorageAccessHandle handle with StorageAccessTypes types and DOMString name, the BroadcastChannel(name) method must run these steps:

  1. If types.all is false and types.BroadcastChannel is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running new BroadcastChannel with name.

2.3.9. Shared Workers

Modify Shared Workers to define the following:

enum SameSiteCookiesType { "all", "none" };

dictionary SharedWorkerOptions : WorkerOptions {
  SameSiteCookiesType sameSiteCookies;
};

The default sameSiteCookies is all in first-party-site context and none otherwise.

Modify SharedWorkerGlobalScope to have an associated SameSiteCookiesType sameSiteCookies.

Modify new SharedWorker to accept SharedWorkerOptions instead of WorkerOptions.

Modify new SharedWorker to add a new step below step 1 as follows:

  1. If options.sameSiteCookies is all and Window's associated document is not first-party-site context, then:

    1. Throw an "InvalidStateError" DOMException.

Modify new SharedWorker to add a new matching criteria in step 10.2.2 as follows:

Modify Processing Model to add a new step below step 10.4 as follows:

  1. Set worker global scope’s sameSiteCookies to options.sameSiteCookies.

Note: The SameSiteCookiesType is used to influence which cookies are sent or read during fetch based on the SameSite cookie attribute. all is only available in first-party-site context and permits SameSite "None", "Lax", and "Strict" cookies to be included (if not blocked for some other reason). none is available in any context and permits only SameSite "None" cookies to be included (if not blocked for some other reason).

Clarify SharedWorker usage of sameSiteCookies in more detail. [Issue #21]

When invoked on StorageAccessHandle handle with StorageAccessTypes types, USVString scriptURL, and DOMString or SharedWorkerOptions options, the SharedWorker(scriptURL, options) method must run these steps:

  1. If types.all is false and types.SharedWorker is false:

    1. Throw an "InvalidStateError" DOMException.

  2. Return the result of running new SharedWorker with scriptURL and options.

3. Security & Privacy considerations

In extending an existing access-granting API, care must be taken not to open additional security issues or abuse vectors relative to comprehensive cross-site cookie blocking and storage partitioning. Except for Service Workers (which will not be supported in this extension) non-cookie storage and communication APIs don’t enable any capability that could not be built with cookie access alone.

For more detailed discussions see The Storage Access API § 6 Privacy considerations and The Storage Access API § 7 Security considerations.

Conformance

Document conventions

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

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

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

This is an example of an informative example.

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

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FILE-SYSTEM-API]
Eric Uhrhane. File API: Directories and System. URL: https://dev.w3.org/2009/dap/file-system/file-dir-sys.html
[FileAPI]
Marijn Kruisselbrink. File API. URL: https://w3c.github.io/FileAPI/
[FS]
Austin Sullivan. File System Standard. Living Standard. URL: https://fs.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[IndexedDB-3]
Joshua Bell. Indexed Database API 3.0. URL: https://w3c.github.io/IndexedDB/
[MEDIA-SOURCE-2]
Jean-Yves Avenard; Mark Watson; Matthew Wolenetz. Media Source Extensions™. URL: https://w3c.github.io/media-source/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SERVICE-WORKERS]
Jake Archibald; Marijn Kruisselbrink. Service Workers. URL: https://w3c.github.io/ServiceWorker/
[STORAGE]
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/
[STORAGE-ACCESS]
Benjamin VanderSloot; Johann Hofmann; Anne van Kesteren. The Storage Access API. URL: https://privacycg.github.io/storage-access/
[STORAGE-PARTITIONING]
Privacy Community Group. Client-Side Storage Partitioning. URL: https://privacycg.github.io/storage-partitioning/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WEB-LOCKS]
Joshua Bell; Kagami Rosylight. Web Locks API. URL: https://w3c.github.io/web-locks/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[RFC6265]
A. Barth. HTTP State Management Mechanism. April 2011. Proposed Standard. URL: https://httpwg.org/specs/rfc6265.html

IDL Index

dictionary StorageAccessTypes {
  boolean all = false;
  boolean cookies = false;
  boolean sessionStorage = false;
  boolean localStorage = false;
  boolean indexedDB = false;
  boolean locks = false;
  boolean caches = false;
  boolean getDirectory = false;
  boolean estimate = false;
  boolean createObjectURL = false;
  boolean revokeObjectURL = false;
  boolean BroadcastChannel = false;
  boolean SharedWorker = false;
};

interface StorageAccessHandle {
  readonly attribute Storage sessionStorage;
  readonly attribute Storage localStorage;
  readonly attribute IDBFactory indexedDB;
  readonly attribute LockManager locks;
  readonly attribute CacheStorage caches;
  Promise<FileSystemDirectoryHandle> getDirectory();
  Promise<StorageEstimate> estimate();
  DOMString createObjectURL((Blob or MediaSource) obj);
  undefined revokeObjectURL(DOMString url);
  BroadcastChannel BroadcastChannel(DOMString name);
  SharedWorker SharedWorker(USVString scriptURL, optional (DOMString or SharedWorkerOptions) options = {});
};

partial interface Document {
  Promise<boolean> hasUnpartitionedCookieAccess();
  Promise<StorageAccessHandle> requestStorageAccess(StorageAccessTypes types);
};

enum SameSiteCookiesType { "all", "none" };

dictionary SharedWorkerOptions : WorkerOptions {
  SameSiteCookiesType sameSiteCookies;
};

Issues Index

Clarify client-side storage mechanism changes in more detail. [Issue #19]
Clarify SharedWorker usage of sameSiteCookies in more detail. [Issue #21]