Tom Teman
Tom Teman

Reputation: 2013

Safari 13+ iframe blocks CORS cookies

Safari flat out doesn't let you set cookies in iframes of domains different than the parent domain, server-side CORS headers be damned.

To clarify: user is on domainA.com. An iframe for domainB.com is open, and attempts to authenticate the user on domainB.com inside the iframe. Set-Cookie header is returned from the server inside the domainB.com iframe, with all the required headers, but Safari isn't sending it back in subsequent calls.

An old workaround was doing a form submit from the iframe, and set the cookie in the response. I guess they liked the fact that the user was clicking something to submit the form. You'd have to poll for the cookie to see when the response came back, as form submits have no callbacks, and in the case of HttpOnly cookies you couldn't, but hey, it worked! Until it didn't.

Then, a more recent workaround was redirecting the user to the iframe domain in a brand new window/tab, setting a random cookie there, and from that moment on, that subdomain was "trusted" inside the iframe. Again, it required a click to open the new window/tab, and there was even a visual indication of the new tab opening. Much security, such standards.

And now, as of Safari 13 - No more workaround. No more secure iframe cookie setting 🤬

Any other authentication scheme isn't good for us (e.g. Auth-X header). We need to use an HttpOnly secure cookie, as we don't want that token to be in any way accessible by javascript client-side.

To be clear, everything works great on any other browser.

Relevant WebKit Bugzilla

Does anyone have any suggestions?

Edit:

Thanks for the link @tomschmidt, that seems like the right direction. I tried using Apple's Storage Access API, but unfortunately, although I'm making sure to request access before initializing my login logic with the API:

requestStorageAccess = async() => {
    return new Promise(resolve => {
      //@ts-ignore
      document.requestStorageAccess().then(
        function () {
          console.log('Storage access was granted');
          resolve(true);
        },
        function () {
          console.log('Storage access was denied');
          resolve(false);
        }
      );    
    });
  }


const storageAccessGranted = await requestStorageAccess();
console.log(storageAccessGranted) // prints 'true'
await login();

Still, the cookies received on the /login API response are not getting sent in subsequent calls to the API :(

Edit 2 (May 2021):

Safari 14 has added another breaking change:

https://webkit.org/blog/11545/updates-to-the-storage-access-api/

Go Apple go! You're making us reminisce about IE6.

Upvotes: 37

Views: 23967

Answers (3)

doodan
doodan

Reputation: 86

example domains:
embeddedOnThisDomain.com (domain the iframe is on)
domainToBeEmbedded.com (domain you're embedding in an iframe)

There is a prerequisite that the user must have interacted (at least one click within the past 30 days) with domainToBeEmbedded.com as a "First Party" - at the top level - outside an iframe (within a popup is currently sufficient).
Meet and Greet the User as First Party

If this has not happened, the browser will not display the popup when initiating document.requestStorageAccess()

Once the prerequisite has been satisfied:
Within the iframe on embeddedOnThisDomain.com (in the code hosted at domainToBeEmbedded.com), you must then

document.requestStorageAccess();
//document.requestStorageAccess() must be initiated by a user click event.
//initiating automatically on page load or similar will not work.

Which will now show a browser popup for the user to allow domainToBeEmbedded.com cookies to be used within the iframe on embeddedOnThisDomain.com

Once the user has accepted, you can check the iframe still has access to cookies by using:
Storage Access API

var promise = document.hasStorageAccess();
promise.then(
  function (hasAccess) {
    // Boolean hasAccess says whether the document has access or not.
  },
  function (reason) {
    // Promise was rejected for some reason.
  }
);

Tested on:
Monterey Safari 15.3
Big Sur Safari 14.1

Upvotes: 4

Matt Cosentino
Matt Cosentino

Reputation: 403

So, the workaround still kinda works, as long as the new window is storing the cookie that you want to store. The iframe still can't store it's own cookies. In my case, all I needed was the session id cookie. So, I open a small popup window when the user grants storage access. It gets and stores the session id cookie, closes, and reloads the iframe. The iframe then has access to the session id cookie and sends it in subsequent requests. I think this is just temporary though, it looks like they're going to remove storage access from popup windows sometime in the future. Maybe they'll fix the iframe not being able to store cookies by then.

Upvotes: 2

tomschmidt
tomschmidt

Reputation: 392

I think I might have found the solution: Apple's Storage Access API: https://webkit.org/blog/8124/introducing-storage-access-api/

Upvotes: 4

Related Questions