Third-Party Cookie Restrictions for Iframes in Safari
What do you need to know before?
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.
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:
document.hasStorageAccess
API DocThis API is used to check cookie storage access. This will return
false
for third-party cookies in the case of the Safari browser.document.requestStorageAccess
API DocThis 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.
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.
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:
needPermission
: This will be true when the browser is Safari and it has support forhasStorageAccess
andrequestStroageAccess
Hint: Use this boolean while consuming this hook to decide when to call
askForPermission
andhaveCheckedPermission
.askForPermission
: This is the function that the consumer could call to request the user to give storage access permissionhaveCheckedPermission
: This is a boolean which will be true after callingaskForPermission
in the case ofneedPermission
is true initially.
To consume the above hook, we have followed the below-mentioned steps:
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 whenneedPermission
isfalse
.We created some other routes
user-access-flow
that show a button with some text likeSet Cookie
. AndonClick
of it, we set the cookie.This is where we are actually calling authentication-related APIs and then the server is setting up cookies.
Once this cookie is set, close this tab using
window.close()
Now when,
needPermission
istrue
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.
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:
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.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!