How to resolve clickjacking prevention issue in Next.js app on Shopify?

Nextjs
Shopify Partner
1 0 0

Hi,

Hi,
I am facing an issue regarding clickjacking prevention. I have implemented authentication and custom headers that contain headers that are requested by Shopify.

 

ctx.set('Content-Security-Policy', `frame-ancestors https://${ctx.query.shop} https://admin.shopify.com`);
ctx.res.setHeader(
"Content-Security-Policy",
`frame-ancestors https://${ctx.query.shop} https://admin.shopify.com;`
)

 

local environment headers: 

Screen Shot 2022-04-28 at 1.06.37 PM.png

 

 

 

Production Environment response headers:

Screen Shot 2022-05-09 at 3.20.20 PM.png

It is working fine in my development environment but is not working in production.

 

my server.js file

/* eslint-disable @typescript-eslint/no-var-requires */
const dotenv = require('dotenv')

const Koa = require('koa')
const next = require('next')
const { default: createShopifyAuth } = require('@shopify/koa-shopify-auth')
const { verifyRequest } = require('@shopify/koa-shopify-auth')
const { default: Shopify, ApiVersion } = require('@shopify/shopify-api')

const { withSentry } = require('@sentry/nextjs')

const proxy = require('koa-proxy')
const Router = require('@koa/router')

const { checkForNewThemeSupport } = require('./checkfordawntheme')


dotenv.config()


const { PORT } = process.env
const { NODE_ENV } = process.env

const port = parseInt(PORT, 10) || 3000
const dev = NODE_ENV !== 'production'
const app = next({ dev })
const handle = withSentry(app.getRequestHandler())


if (!process.env.NEXT_PUBLIC_SHOPIFY_API_KEY || !process.env.SHOPIFY_API_SECRET_KEY) {
  console.error('Missing api keys')
}


const SCOPES = [
  'read_products',
  'write_products',
  'unauthenticated_read_product_listings',
  'read_orders',
  'read_script_tags',
  'write_script_tags',
  'read_themes'
]


Shopify.Context.initialize({
  API_KEY: process.env.NEXT_PUBLIC_SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET_KEY,
  SCOPES,
  HOST_NAME: process.env.SHOPIFY_APP_URL.replace(/https:\/\//, ''),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage()
})


// TODO replace this with something serious
const ACTIVE_SHOPIFY_SHOPS = {}


const handleRequest = async (ctx) => {
  await handle(ctx.req, ctx.res)
  ctx.set('Content-Security-Policy', `frame-ancestors https://${ctx.query.shop} https://admin.shopify.com`);
  ctx.res.setHeader(
      "Content-Security-Policy",
      `frame-ancestors https://${ctx.query.shop} https://admin.shopify.com;`
    )
    console.log("************* frame ancestor ********* ")
  ctx.respond = false
  ctx.res.statusCode = 200
}

app.prepare().then(() => {
  const server = new Koa()
  const router = new Router()

  server.keys = [Shopify.Context.API_SECRET_KEY]

  // online auth for app/user request
  server.use(createShopifyAuth({
    accessMode: 'online',
    afterAuth(ctx) {
      // Online access mode access token and shop available in ctx.state.shopify
      const { shop } = ctx.state.shopify
      const { host } = ctx.query

      // Redirect to app with shop parameter upon auth
      ctx.redirect(`/?shop=${shop}&host=${host}`)
    }
  }))

  // offline auth for background tasks
  server.use(createShopifyAuth({
    accessMode: 'offline',
    prefix: '/offline',
    async afterAuth(ctx) {
      const { shop, accessToken } = ctx.state.shopify

      ACTIVE_SHOPIFY_SHOPS[shop] = true

      // APP_UNINSTALLED webhook to make sure merchants go through OAuth if they reinstall it
      const response = await Shopify.Webhooks.Registry.register({
        shop,
        accessToken,
        path: '/webhooks',
        topic: 'APP_UNINSTALLED',
        webhookHandler: async (topic, shop) => delete ACTIVE_SHOPIFY_SHOPS[shop]
      })

      if (!response.success) {
        console.error(`Failed to register APP_UNINSTALLED webhook: ${response.result}`)
      }

      ctx.redirect(`/auth?shop=${shop}`)
    }
  }))

  router.get('/', async (ctx) => {
    const { shop } = ctx.query

    if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
      ctx.redirect(`/offline/auth?shop=${shop}`)
    } else {
      await handleRequest(ctx)
    }
  })

  router.get('(/_next/static/.*)', handleRequest)
  router.get('/_next/webpack-hmr', handleRequest)
  router.get('/(.*).js', handleRequest)

  router.get('/login', verifyRequest(), handleRequest)
  router.get('/register', verifyRequest(), handleRequest)

  router.post(
    '/webhooks',
    async (ctx) => {
      try {
        await Shopify.Webhooks.Registry.process(ctx.req, ctx.res)
      } catch (error) {
        console.error(`Failed to process webhook: ${error}`)
      }
    }
  )

  router.post(
    '/graphql',
    verifyRequest({ returnHeader: true }),
    async (ctx) => {
      await Shopify.Utils.graphqlProxy(ctx.req, ctx.res)
    }
  )

  router.get(
    '/checkfor20',
    verifyRequest(),
    async (ctx) => {
      try {
        const hasCartAppBlock = await checkForNewThemeSupport(ctx)

        ctx.body = JSON.stringify({ hasCartAppBlock })
        ctx.status = 200
      } catch (error) {
        console.error(`Failed to check for theme: ${error}`)
      }
    }
  )

  server.use(router.allowedMethods())
  server.use(router.routes())

  server.use(proxy({
    host: process.env.NEXT_PUBLIC_TREEPOINTS_API_URL,
    match: /^\/********-api\/\w*/,
    map: (path) => path?.split(process.env.NEXT_PUBLIC_TREEPOINTS_API_PROXY_URL)?.[1] || path
  }))

  // eslint-disable-next-line no-console
  server.listen(port, () => console.log(`> Ready on http://localhost:${port}`))
})

App development framework: nextjs
deployment server: Heroku

 

Any help would be appreciated 

Replies 0 (0)