FROM CACHE - en_header

Embedded app wont load in Safari - no Cookies

Shaibt
Shopify Partner
27 1 18

Our embedded app won't load in Safari (13.1.2):

[Error] Unrecognized Content-Security-Policy directive 'worker-src'.

[Error] Refused to load https://XXXX.myshopify.com/admin/auth/login because it does not appear in the frame-ancestors directive of the Content Security Policy.

 

Seems the issue is is happening while still in Shopify flow.

We're using  app-bridge-react Provider component to embed in Shopify admin.

Our embedded app doesn't use cookies so I don't think this thread is relevant to our problem  - unless I've missed something.

 

Replies 13 (13)
instaconnect
Shopify Partner
5 0 0

Hi Shaibt, did you find a solution for this?  We are experiencing the same issue.

Shaibt
Shopify Partner
27 1 18

Hi @instaconnect , unfortunately - no, haven't found a solution yet. This has worked properly before but at some point stopped to.

Think that Safari updates are outpacing some of the Shopify iframe mechanisms.

eCreateStudio
Tourist
4 0 2

Hi @Shaibt, I'm also finding this issue.

It seems to be due to the Response header settings:

Content-Security-Policy: frame-ancestors 'none'

And possibly: 

X-Frame-Options: DENY

Which relate to the redirect response:

Redirect Response 303
Location: https://XXXX.myshopify.com/admin/auth/login
 
But I'm not sure so far where the headers for this response are set, and suspect its on the Shopify side anyway.
 
Pretty frustrating, my app works in Chrome and Firefox, but no joy in Safari, which is holding up my app approval.
olivert
Explorer
51 11 18

Another post with similar issue

https://community.shopify.com/c/Shopify-APIs-SDKs/Unrecognized-Content-Security-Policy-directive-wor...

Had anyone ever had apps working on safari?

Seems it blocks iframes 

 

axis80
Shopify Expert
20 1 4

Same here. My app does not use cookies, so solutions like this do not apply.

I am still trying to wrap my head around the issue, but what I think is happening is this:

  1. When the Shopify admin page is loaded in the browser, Shopify's web server is sending along a Content-Security-Policy header containing the "worker-src" string which is unrecognized / unsupported in Safari.

  2. Because Safari doesn't recognize that string, it defaults to the strictest possible interpretation, which is not to allow loading of iframes from external sources.  Thus, the iframe that loads the embedded app does not get rendered.

Does that explanation sound at all accurate?  Or am I off base?

 

 

sillycube
Shopify Partner
700 16 106

Any update in 2021?

My app also cannot work in Safari 13.1.3

BYOB - Build Your Own Bundles, SPO - SEO App to research keywords & edit social link preview
geraldo2
Shopify Partner
2 0 0

I'm experiencing the same issue in Safari 14.1.

Rishabh_Tayal
Shopify Partner
18 0 3

Having same issue. Still no solution.

Webdibs
Shopify Partner
29 2 7

Any updates? I'm having the same issue.

Stretching the limits of Shopfy, Wordpress & WooCommerce since 2010.
Patrick_Hughes
Shopify Expert
24 0 8

You may be getting this error during OAuth by redirecting to the authorisation url https://{shop}.myshopify.com/admin/oauth/authorize from inside the iframe without escaping it. This happened to me and for some reason it only causes this error in Safari. To get around this, you need to use app bridge to escape the iframe, which can be done in Node JS (for example) by replacing something like:

 

res.redirect(authUrl)

 

with the following:

 

const html = (shop, apiKey, url) => {
  return `<html>
    <head>
      <script src="https://unpkg.com/@shopify/app-bridge"></script>
      <script>
        var AppBridge = window['app-bridge'];
        var createApp = AppBridge.createApp;
        var actions = AppBridge.actions;
        var Redirect = actions.Redirect;

        if (window.top == window.self) {
          window.location.assign('${url}');
        } else {
          var app = createApp({ apiKey: '${apiKey}', shopOrigin: '${shop}' });
          Redirect.create(app).dispatch(Redirect.Action.REMOTE, '${url}');
        }
      </script>
    </head>
    <body>
      <p>Error: Failed to escape iframe during OAuth</p>
    </body>
  </html>`
}

res.send(html(shop, apiKey, authUrl))

 

After making this change you may have a redirect loop that keeps repeating OAuth if you have forceRedirect set to true in the config for Shopify App Bridge. I couldn't find documentation for this, but it seems that forceRedirect causes a redirect to the App URL path that you specified in the Partner Dashboard ( restarting OAuth ) rather than the URL where you loaded and created an instance of app bridge. To get around this you can skip OAuth by redirecting to your apps homepage if you already have an access token for the merchant during Step 3 of OAuth.

 

DiscountNinja
Shopify Partner
101 0 45

Hi @Shaibt and others who run into this issue.

We ran into the same issue. I'll describe here how we resolved it.

Problem

On Safari only, the browser refuses to redirect to /admin/oauth/authorize (as mentioned by @Patrick_Hughes). It fails with the error mentioned by the OP related to the CSP.

Context
In our case, this happened because we redirect to the oAuth flow from the server. In other words, the browser redirects to an endpoint of our app, which decides that control should be given to Shopify to perform an oAuth check (check/grant permissions). This means the browser received a 302 (redirect) instructing it to go to the oAuth flow, which is a Shopify endpoint. Safari does not allow this. It seems other browsers have no problems with it.

Solution
The solution we implemented was to replace our redirect with an API call, which returns the URL for the oAuth flow. We then use the AppBridge (as mentioned by @Patrick_Hughes) to redirect the merchant to the URL received from the API.
The JavaScript function that handles this is conceptually implemented as follows:

 

 

//Note: this excerpt is meant as a draft/example only
function RedirectToAuthorizationFlow(apiKey, host, forceRedirect) {  
  //Change this logic to use your own endpoint here
  fetch(yourendpointhere)
    .then(response => response.json())
    .then(data => 
       //Get the URL to which we need to redirect the merchant 
       let url = data.url;
       
       //Instantiate the AppBridge
       let createApp = window['app-bridge']['default'];
       createApp({ apiKey: apiKey, host: host, forceRedirect: forceRedirect });

       //Redirect to remote URL
       let redirect = Redirect.create(shopifyAppBridgeApp);
       redirect.dispatch(Redirect.Action.REMOTE, { url: url });
}

 

 

 

Bart Coppens | Limoni Apps | Building apps for Shopify since 2016
Shaibt
Shopify Partner
27 1 18

Hi @DiscountNinja , @Patrick_Hughes ,

Seems like you've hit the nail on the head. 

Our app, similar to described, redirected to the Shopify OAuth flow on the server side (using a 302 redirect as @DiscountNinja describes) and that probably was the root cause of this error. I guess there's something in the Shopify OAuth response headers post redirect that Safari doesn't "like" in this context (iFrame).

 

We've since abandoned the embedded app approach for our Shopify app but I hope your solutions will help others facing same problem.

 

RyanAM
Shopify Partner
3 0 3

Had the same issue and that fixed it, thank you @DiscountNinja!

 

What we did was simply change the `res.redirect(shopifyAuthUrl)` in NestJS to returning HTML with a full-frame Javascript redirect similar to what would be necessary to update requested scopes.

 

We use the user-agent to detect:

  • Is it Firefox?
  • Is it Chrome?

...if it is, then we use the standard 301 redirect. If it's notor if the lowercased user-agent contains "shopify" then we use the Javascript redirect. The 301 results in a more pleasant user experience (the initialization process doesn't visibly jump from site to site) so we wanted to ensure that's preserved for most users instead of doing a Javascript redirect for everyone.

 

We also have a force full-frame redirect boolean in case we update scopes in the future as well.

 

The reason for the "shopify" part is because in addition to Safari, our embedded app wasn't loading within the Shopify Android app and they were nice enough to use a custom user-agent to let developers know that it was being accessed from their app which was extremely helpful in resolving this.

 

This could definitely use a bit of cleaning up but here's what we have in place now (inside a NestJS controller):

 

 

 

@Get('install')
getShopifyInstallation(@Query('shop') shopDomain: string, @Req() req, @Res() res) {
  // Verify the Shopify domain.
  if (!this._shopifyAuthService.validateShopDomain(shopDomain)) {
    throw new BadRequestException('Invalid shop domain.');
  }

  // Note that if we need to change scopes in the future, a full-frame redirect will be necessary. Details: https://shopify.dev/apps/auth/oauth#changes-to-granted-scopes
  const isForceFullFrameRedirect = false;
  const redirectUrl = this._shopifyAuthService.generateInstallationUrl(shopDomain);
  const userAgent = req.headers['user-agent'] ? req.headers['user-agent'].toLowerCase() : '';

  if (
    isForceFullFrameRedirect ||
    (userAgent.indexOf('chrome') === -1 && userAgent.indexOf('firefox') === -1) ||
    userAgent.indexOf('shopify') > -1
  ) {
    return res.send(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>Redirecting, please wait...</title>
          <script>
            setTimeout(()=>{
              window.top.location="${redirectUrl}";
            }, 4000);
          </script>
          <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" />
          <style type="text/css">
            body {
              padding: 30px;
              background-color: #f6f6f7;
              font-family: helvetica;
            }
          </style>
        </head>
        <body>
            <i class="fa fa-spinner fa-spin"></i> &nbsp; Please consider using the Chrome browser to access the app. Redirecting...
        </body>
      </html>
    `);
  }

  return res.redirect(redirectUrl);
}