App reviews, troubleshooting, and recommendations
Hi there,
Requirement that must be met before initial screening.
App must set security headers to protect against click jacking.
Your app must set the proper frame-ancestors content security policy directive to avoid click jacking attacks. The 'content-security-policy' header should set frame-ancestors https: //[shop].myshopify.com https://admin.shopify.com, where [shop] is the shop domain the app is embedded on.
Can anyone please help in resolving this issue. I am not able to resolve it please provide coding or solution to this .
i have same problem 😞 , any solution ?
Having the same issue. Couldn't able to find any solution
If you come up with any solution please let me know thanks
Sure !!
Thanks Likewise 🙂
Hi there,
Like they mentioned in the initial screening you need to add CSP headers from your server side.
https://shopify.dev/apps/store/security/iframe-protection
The code is depends on the programming language you use for your app.
You need to set a header with the current requesting store domain.
I am using PHP as my backend. Can you pls check is this format ?
header('X-Frame-Options:self');
<Content-Security-Policy: frame-ancestors 'self'>
or
<Content-Security-Policy: frame-ancestors 'https://[shop].myshopify.com'>
Should I use this header for all the pages in my app or for particular index page which connects to the iframe?
Hey there. I'm experiencing the same problem. What worked for you?
If you are using React with Koa this is the solution (all changes are made in server.js):
I'm using the npm package koa-helmet (v6.1.0) (not necessary, can also be done without)
import helmet from "koa-helmet";
and almost right after the app.prepare statement, I have defined a new middleware function called setContentSecurityHeader
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
const setContentSecurityHeader = (ctx, next) => {
// Cookie is set after auth
if (ctx.cookies.get("shopOrigin")) {
return helmet.contentSecurityPolicy({
directives: {
defaultSrc: helmet.contentSecurityPolicy.dangerouslyDisableDefaultSrc,
frameAncestors: [
`https://${ctx.cookies.get("shopOrigin")}`,
"https://admin.shopify.com",
],
},
})(ctx, next);
} else {
// Before auth => no cookie set...
return helmet.contentSecurityPolicy({
directives: {
defaultSrc: helmet.contentSecurityPolicy.dangerouslyDisableDefaultSrc,
frameAncestors: [
`https://${ctx.query.shop}`,
"https://admin.shopify.com",
],
},
})(ctx, next);
}
};
server.use(setContentSecurityHeader);
And as a final step, I initialize a cookie right after authentication. This cookie ensures, that the header is set correctly for each following request after authentication.
server.use(
createShopifyAuth({
async afterAuth(ctx) {
const { shop, accessToken, scope } = ctx.state.shopify;
// set shopOrigin cookie, so it can be used for click jacking header
ctx.cookies.set("shopOrigin", shop, {
httpOnly: false,
secure: true,
sameSite: "none",
});
const host = ctx.query.host;
ACTIVE_SHOPIFY_SHOPS[shop] = scope;
Hope this helps.
Other resources:
https://shopify.dev/apps/store/security/iframe-protection
Thank you for your response, Denis. As i see, we must set this header in each endpoint concerned to Shopify. Am i right?
Exactly.
My problem was that the header was not set for each and every request...
Sure! And I still have the problem. I think the preflight check (OPTIONS verb) is my headache. Let's keep this post up to date to help other devs to save time
I finally made it. Using express i created a middleware to write Content-Security-Policy header as they required on absolutely ALL endpoints, with the next code:
export const CspMiddleware = async (req: Request, res: Response, next: NextFunction) => {
const merchant = req.query?.shop.toString() as string;
res.setHeader('Content-Security-Policy', `frame-ancestors ${merchant} https://admin.shopify.com;`);
next();
};
And i had to create an options route to cover the preflight-check requests made by shopify, with the next code:
api.options('*', CspHeaderMiddleware, your-controller-here);
Remember: you have to place this endpoint on top of all of the other endpoints in file. Cheers
Dennis, thanks for posting, super helpful!
This seems to work together with @miguelcabgil solution.
The problem that I'm facing now is that if I restart the server, and try to use the app again, it goes in a redirect auth loop until I clear cookies. I imagine that if I ever have to restart the app after it's deployed, customers will probably get stuck in the loop. I don't understand how are the cookies causing a loop.
Hello there,
I just wanted to update you all, that this is not a problem anymore if you generate your project with the most recent Shopify-CLI.
The code is already there and it looks like this:
app.use((req, res, next) => {
const shop = req.query.shop;
if (Shopify.Context.IS_EMBEDDED_APP && shop) {
res.setHeader(
"Content-Security-Policy",
`frame-ancestors https://${shop} https://admin.shopify.com;`
);
} else {
res.setHeader("Content-Security-Policy", `frame-ancestors 'none';`);
}
next();
});
The code looks very different in comparison to the older versions, so if you want to look into that in detail, just get the newest Shopify CLI version and look into it yourself.
Happy that Shopify reacted to feedback and now we don't have to deal with that problem anymore.
I used that latest Shopify CLI with the above code but still receive clickjacking rejection.
And @miguelcabgil suggested installing the header on ALL endpoints, I guess it includes API endpoints too.
hi @sonchu , have you tried setting up the header on all endpoints and got approval?
I did.
Learn how to expand your operations internationally with Shopify Academy’s learning path...
By Shopify Feb 4, 2025Hey Community, happy February! Looking back to January, we kicked off the year with 8....
By JasonH Feb 3, 2025Expand into selling wholesale with Shopify Academy’s learning path, B2B on Shopify: Lau...
By Shopify Jan 28, 2025