Updates to the Storage Access API

The Storage Access API allows third-party web content to ask for permission to get access to its unpartitioned storage, typically in order to authenticate the user. In the case of Safari and WebKit, using the Storage Access API enables cookie access under Intelligent Tracking Prevention.

This blog post covers two changes to the Storage Access API in Safari and WebKit as well as a how-to guide on adoption based on questions we’ve been asked the last two years.

Changes to API Functionality

iOS and iPadOS 14.5, and macOS Big Sur 11.3 betas feature two sought after changes to the Storage Access API in Safari and WebKit – per-page storage access and support for nested iframes. Both of these changes were driven by the standards process in W3C Privacy CG.

Per-Page Storage Access

If a request for storage access is granted to embedee.example, access is now granted to all embedee.example resource loads under the current first party webpage. This includes sibling embedee.example iframes but also other, non-document resources.

Nested Iframes Can Request Storage Access

Imagine a webpage embedding a cross-site iframe from embedeeOne.example which in turn embeds a cross-site iframe from embedeeTwo.example which makes the latter a so called nested iframe. As of this release, nested iframes such as embedeeTwo.example are also allowed to request storage access. Note that we may require first parties to explicitly delegate this capability through Permissions Policy at a later stage. Mozilla has expressed an interest in such control.

How To Use the Storage Access API

For the purposes of this guide we will use the domains social.example for the embedded content in need of cookie access and news.example as the first party website embedding social.example.

First, Cross-Site Iframes Call the API

The Storage Access API is called from inside cross-site, or third-party, iframes. You don’t have to call the API if your website is first party and first party websites cannot call the API on behalf of third-parties.

How-To #1: Meet and Greet the User as First Party

If you want to make use of the Storage Access API as a third-party, you first need to take these steps as a first party:

  1. Make sure you are using regular browsing mode, i.e. not Private Browsing. We will cover Private Browsing at the end of this guide.
  2. Take the user to your domain as first party. This is your website showing itself and giving the user a chance to recognize your brand and domain name. Recognition is important since the prompt for storage access features your embedded iframe’s domain. In our example, this is taking the user to a webpage with social.example in the URL bar, either though a navigation or a popup.
  3. Have the user interact (tap, click, or use the keyboard) with your website as first party. This tells the browser that the user has actually seen and used the site. Note: Navigating to and from your website in a redirect without user interaction does not count. Formally, WebKit’s requirement is user interaction as first party the last 30 days of browser use. Being granted storage access through the Storage Access API counts as such user interaction. In our example, this is having the user tap/click on the webpage with social.example in the URL bar.
  4. Set cookies when you are first-party. This establishes the website as “visited” for the purposes of the underlying cookie policy. Third parties without cookies cannot set cookies in Safari and never have since Safari 1.0 in 2003. This means you cannot use the Storage Access API as third-party until you have set at least one cookie as first party. In our example, this is setting cookies for social.example with social.example in the URL bar.

The above requirements are there to make sure the sometimes 50-100 embedded third-parties on a single webpage cannot all prompt the user for storage access, only the ones the user has visited and interacted with can.

How-To #2: Use the Storage Access API as Third Party

Once you have had the user interact with your website as first party and have set cookies as first party, you are ready to make use of the Storage Access API.

  1. In shipping Safari, your cross-site iframe that is about to request storage access must be a direct child frame of the top frame. Nested iframes can request storage access as of iOS 14.5 and macOS 11.3 (currently in beta).
  2. Make your cross-site iframe call document.hasStorageAccess() as soon as it’s rendered to check your status. Note: Don’t call this function upon a user gesture since it’s asynchronous and will consume the gesture. Once the user gesture is consumed, making a subsequent call to document.requestStorageAccess() will fail because it’s not called when processing a user gesture. In our example this is social.example‘s iframe.
  3. If document.hasStorageAccess() returns false, your iframe doesn’t have storage access. Now set an event handler on elements that represent UI which requires storage access and make the event handler call document.requestStorageAccess() on a tap or click. This is the API that requires a user gesture. In our example this is social.example‘s iframe calling the API.
  4. Render the page with your cross-site iframe. Tap or click on an element with an event handler in the iframe. In our example this is rendering a page from news.example with thesocial.example‘s iframe and clicking on an element in the social.example iframe’s document.
  5. If the user has not yet opted in to storage access for social.example under news.example there will now be a prompt. Choose “Don’t Allow” in the prompt. Tip: Don’t choose “Allow” yet because it’ll be remembered and you’ll have to delete browser history to reset it. If you are not getting the prompt, you either have not gotten user interaction as first party and set cookies for social.example yet (see How-To #1) or you have already chosen “Allow” earlier which is remembered.
  6. Test the behavior for the “Don’t Allow” case. You can do it repeatedly. Do it until you’re happy with how your code handles it. Note that when the user chooses “Don’t Allow” in the prompt, their user gesture is consumed and any further API calls in your iframe that require a user gesture will have to get the user to tap or click again. We’ve deliberately designed it this way to make sure that an explicit deny from the user doesn’t trigger further privileged API calls from the iframe. The user should at this point be able to continue with other things.
  7. Now tap or click the iframe again and this time choose “Allow” in the prompt. This should open up cookie access on resolution of the promise.
  8. Test the behavior for the “Allow” case. Note that when the user chooses “Allow” in the prompt, their user gesture is preserved and any further API calls in your iframe that require a user gesture can go right ahead. We’ve deliberately designed it this way so that when you get access to cookies and note that the user is not in the desired state, such as not logged in, you can open a popup or navigate them to your website without further user gestures. In our example this would be a popup or navigation to social.example.
  9. Now reload the webpage. This will reset your per-page storage access. Tap or click the iframe to trigger the call to document.requestStorageAccess(). This should open up cookie access without a prompt since the user has already opted in and that choice is remembered.
  10. Finally test the flow in Private Browsing Mode. In that mode, the user must interact with your website as first party (see How-To #1) in the same tab as where you later request storage access as third-party. This is because Private Browsing Mode uses a separate ephemeral session for each new tab the user opens, i.e. the state of those tabs are separate. The rest should work the same as in regular mode.