Third-Party Cookie Restrictions for Iframes in Safari

Photo by pine watt on Unsplash

Third-Party Cookie Restrictions for Iframes in Safari

What do you need to know before?

  1. What is an iframe?

  2. What are cookies?

  3. What is a third-party cookie?

Introduction

Have you ever wondered why some websites ask for storage permissions or why some features don't work in certain browsers? Let's dive into the intricacies of third-party cookie restrictions in Safari and how we tackled them!

Safari, Apple's flagship browser, has always been a pioneer in the quest for user privacy and security. In recent years, it has made significant strides in protecting its users from the prying eyes of online trackers by restricting third-party cookies.

In the following discussion, we will delve into a specific challenge encountered within this privacy-focused paradigm. This blog aims to unravel the complexities of seeking storage permission in Safari, particularly when our content is loaded within an iframe and relies on third-party cookies.

The predicament arose as we endeavored to integrate our React-based web app into an iframe hosted on a different domain. This intersection of domains posed a unique challenge, prompting us to explore innovative solutions to seamlessly navigate the intricacies of Safari's third-party cookie restrictions.

💡
WebKit is an open-source web browser engine developed by Apple Inc. It is primarily used as the rendering engine for Apple's Safari web browser. In the next few sections, we have added links to Webkit's website.

Problem

Our application uses cookies for authentication. In one of the use cases, it was being rendered inside an iframe. The parent HTML document that was rendering the iframe was being hosted on an entirely different domain.

In other browsers, our application within the iframe was able to access the cookies but not in Safari. Safari uses Intelligent Tracking Prevention(ITP) to control the access of third-party cookies.

ITP aims to prevent third-party cookies, making them inaccessible in iframes unless certain conditions are met. These conditions can be found in the Webkit's official announcement.

Storage Access APIs

As per ITP

"Third-party cookie access can only be granted through the Storage Access API."

Let's look at parts of this API that concern our problem:

  1. document.hasStorageAccess API Doc

    This API is used to check cookie storage access. This will return false for third-party cookies in the case of the Safari browser.

  2. document.requestStorageAccess API Doc

    This API is used to ask for third-party storage(cookie) access explicitly from the user.

Both of the above APIs are available in Safari as well as in other browsers.

Webkit's official documentation explains the steps to use these APIs & the rest of the user flow(which is the basis for the following solution). We recommend giving it a read before moving ahead with this post.

Solution

The solution described below is not the only one but will help you in designing solutions for your use cases. You can also design a solution as per your needs by following the guide mentioned here.

We are using react so the above-mentioned solution is written in the concepts of react.

  1. We have created separate utility functions in the helper file.

     export function isSafari(): boolean{
       const userAgent = navigator.userAgent.toLowerCase();
       return (
         userAgent.indexOf("safari") !== -1 && userAgent.indexOf("chrome") === -1
       );
     };
    
     function supportStorageAccessApi(): boolean {
       return "hasStorageAccess" in document && "requestStorageAccess" in document;
     }
    
     export function hasStorageAccess(): Promise<boolean> {
       return document.hasStorageAccess();
     }
    
     export function requestStorageAccess(): Promise<void> {
       return document.requestStorageAccess();
     }
    
     export function requiresStoragePermissions(): boolean {
       return isSafari() && supportStorageAccessApi();
     }
    

    The above code is to make the browser API abstract from the actual implementation. These functions are what we are going to call.

  2. Then after, we created a new file named useStoragePermissions.tsx and added the below-mentioned code.

     export const useStoragePermissions = (): {
       needPermission: boolean;
       askForPermission: () => void;
       haveCheckedPermission: boolean;
     } => {
       const [needPermission, setNeedPermission] = React.useState(
         requiresStoragePermissions() ? true : false
       );
       const [haveCheckedPermission, setHaveCheckedPermission] =
         React.useState(false);
    
       const isHavingPermissionFn = useCallback(async () => {
         try {
           return await hasStorageAccess();
         } catch (e: any) {
           // Handle error gracefully and show user some message
           return false;
         }
       }, []);
    
       const checkPermission = useCallback(() => {
         isHavingPermissionFn().then((isHavingPerm: boolean) => {
           setNeedPermission(!isHavingPerm);
           setHaveCheckedPermission(true);
         });
       }, [isHavingPermissionFn]);
    
       const askForPermission = useCallback(async () => {
         try {
           await requestStorageAccess();
           checkPermission();
         } catch (e: any) {
           // Handle error gracefully and show user some message
         }
       }, [checkPermission]);
    
       React.useEffect(() => {
         if (requiresStoragePermissions()) {
           checkPermission();
         }
       }, [checkPermission]);
    
       return {
         needPermission,
         askForPermission: requiresStoragePermissions()
           ? askForPermission
           : () => {},
         haveCheckedPermission
       };
     };
    

    Using the above hook we have exposed below three states:

    1. needPermission: This will be true when the browser is Safari and it has support for hasStorageAccess and requestStroageAccess

      Hint: Use this boolean while consuming this hook to decide when to call askForPermission and haveCheckedPermission .

    2. askForPermission: This is the function that the consumer could call to request the user to give storage access permission

    3. haveCheckedPermission : This is a boolean which will be true after calling askForPermission in the case of needPermission is true initially.

  3. To consume the above hook, we have followed the below-mentioned steps:

    1. We have mounted and created a hook in #2 at the initialization part of the app. Use the needPermission state from it and proceed ahead as normal when needPermission is false.

    2. We created some other routes user-access-flow that show a button with some text like Set Cookie . And onClick of it, we set the cookie.

      1. This is where we are actually calling authentication-related APIs and then the server is setting up cookies.

      2. Once this cookie is set, close this tab using window.close()

    3. Now when, needPermission is true

      1. We show the user some text like, "Click here and click on the Set-Cookie button on the newly opened tab" and on click of it, redirect the user to the route created in above step 2.

      2. Now, when a user comes back from that route, call askForPermission on click of some button. Which should ask the user to give storage permission.

        Ex:

        Storage access permission ask popup in safari

      3. Once the user clicks on Allow the button, your website is authorized to store and access third-party cookies and now you can continue with business logic.

      4. We have also kept this thing in mind that this consent will be revoked if the user cleans up the browser history and does not visit that domain for 7 days. These constraints are already mentioned here

Conclusion

If you have faced such issues while developing or browsing such issues, please share those in the comments. We will be more than happy to read and comment more on those.

We are hiring!

If solving challenging problems at scale in a fully remote team interests you, head to our careers page and apply for the position of your liking!