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