Access a community of over 900,000 Shopify Merchants and Partners and engage in meaningful conversations with your peers.
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.
Hi Shaibt, did you find a solution for this? We are experiencing the same issue.
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.
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:
Another post with similar issue
Had anyone ever had apps working on safari?
Seems it blocks iframes
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:
Does that explanation sound at all accurate? Or am I off base?
I'm experiencing the same issue in Safari 14.1.
Having same issue. Still no solution.
Any updates? I'm having the same issue.
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.
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 });
}
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.
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:
...if it is, then we use the standard 301 redirect. If it's not, or 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> Please consider using the Chrome browser to access the app. Redirecting...
</body>
</html>
`);
}
return res.redirect(redirectUrl);
}
User | RANK |
---|---|
5 | |
4 | |
4 | |
4 | |
3 |