Embedded apps that rely on 3rd party cookies are prohibited from the Shopify App Store

irochi
New Member
1 0 2

Hello,

We recently submit  our app to revision process and was rejected because ask for manually enable cookies when is installed. 

irochi_0-1620160870142.png

but, my surprise is that we are using the shopify-cli node app boilerplate, the auth flow intact, even a clean repo (shopify create command from cli) and install in dev store have the same behaviour.

I read in severals shopify dev about shopify-cli and is a great tool. The node app boilerplate is awesome too, but I don't understand how this is possible, that has a behaviour that is not allowed. Maybe I'm wrong. That's why i ask for help here. 

Here some sample code currently used:

 

// server.js

...

const handle = app.getRequestHandler();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES.split(','),
  HOST_NAME: process.env.HOST.replace(/https:\/\//, ''),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

app.prepare().then(async () => {
  const server = new Koa();
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];

  server.use(koaBody());

  server.use(
    shopifyAuth({
      accessMode: 'offline',
      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shop, accessToken, scope } = ctx.state.shopify;
       
        const shopData = await addShop({
          name: shop,
          scope,
          accessToken,
        }); // persist shop data

        await setupWebhooks(shop, accessToken); // Here register webhooks
        await checkSubscriptionAndRedirect(shopData, ctx); // handle app subscription and redirect
      },
    }),
  );

  const handleRequest = async ctx => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  };

  router.get('/', async ctx => {
    const shopOrigin = ctx.query.shop;
    const shop = await findShopByName(shopOrigin); // get shop from db

    if (!shop) {
      ctx.redirect(`/auth?shop=${shopOrigin}`);
    } else {
      await handleRequest(ctx);
    }
  });

  router.get('(/_next/static/.*)', handleRequest) // Static content is clear
    .get('/_next/webpack-hmr', handleRequest) // Webpack content is clear
    .get('(.*)', verifyRequest({ accessMode: 'offline' }), handleRequest); // Everything else must have sessions

  server.use(router.allowedMethods())
    .use(router.routes())
    .use(helmet())
    .listen(port, () => {
      console.log(` -> Ready on http://localhost:${port}`);
    });
});

 

And _app.js

...

function userLoggedInFetch(app) {
  const fetchFunction = authenticatedFetch(app);

  return async (uri, options) => {
    const response = await fetchFunction(uri, options);

    if (
      response.headers.get('X-Shopify-API-Request-Failure-Reauthorize') === '1'
    ) {
      const authUrlHeader = response.headers.get(
        'X-Shopify-API-Request-Failure-Reauthorize-Url',
      );

      const redirect = Redirect.create(app);
      redirect.dispatch(Redirect.Action.APP, authUrlHeader || '/auth');
      return null;
    }

    return response;
  };
}

function MyProvider(props) {
  const app = useAppBridge();

  const client = new ApolloClient({
    link: new HttpLink({
      credentials: 'include',
      fetch: userLoggedInFetch(app),
    }),
    cache: new InMemoryCache(),
  });

  const { Component } = props;

  return (
    <ApolloProvider client={client}>
      <Component {...props} />
    </ApolloProvider>
  );
}

class MyApp extends App {
  render() {
    const {
      Component, pageProps, shopOrigin, accessToken,
    } = this.props;

    return (
      <AppProvider i18n={translations}>
        <Provider
          config={{
            apiKey: API_KEY,
            shopOrigin: shopOrigin,
            forceRedirect: true,
          }}
        >
          <ClientRouter />
          <RestApiContext.Provider value={{ shopOrigin, accessToken }}>
            <AuthProvider>
              <UserProvider>
                <ProductProvider>
                  <MyProvider Component={Component} {...pageProps} />
                </ProductProvider>
              </UserProvider>
            </AuthProvider>
          </RestApiContext.Provider>
        </Provider>
      </AppProvider>
    );
  }
}

MyApp.getInitialProps = async ({ ctx }) => ({
  shopOrigin: ctx.query.shop,
});

export default MyApp;

 

Also I'm using an axios instance to perform requests.

import axios from 'axios';
import { getSessionToken } from '@shopify/app-bridge-utils';

const axiosInstance = axios.create();
// intercept all requests on this axios instance
axiosInstance.interceptors.request.use(function (config) {
  return getSessionToken(window.app) // requires an App Bridge instance
    .then((token) => {
      // append your request headers with an authenticated token
      config.headers['Authorization'] = `Bearer ${token}`;
      return config;
    });
});
// export your axios instance to use within your app
export default axiosInstance;

 

I would appreciate a little help or guide to the right path. Thanks in advance!