Shopify / shopify-app-template-remix

359 stars 150 forks source link

No admin object for graphQL with authenticate.webhook(request) #796

Closed FynnMenkIT closed 3 months ago

FynnMenkIT commented 3 months ago

Hey unfortunate it´s me again.

So here is my update. I still need to try the v3 and also the webhook subscription( as far as toml config for webhooks only works for the v3). But it has to work with the v2, or here would be a lot more open issues I guess

My action in the test-endpoint.jsx file looks like the following:

export const action = async ( {request}) => {
  console.log('test-endpoint_fired', request);

  const { topic, shop, session, admin, payload } = await authenticate.webhook(request);
  console.log(await db.session.findMany({ where: { shop } }))
  console.log('test-endpoint_admin', admin);
    console.log('test-endpoint_rest', { topic, shop, session, admin, payload });

  if (!admin) {
    throw new Response();
  }
  if (admin) {
    const data = await admin.graphql(
      `#graphql
        query shopInfo {
          shop {
            name
            url
            myshopifyDomain
            plan {
              displayName
              partnerDevelopment
              shopifyPlus
            }
          }
        }
      }`
    );
  }
  ...

I was able to trigger the webhook via cli and it was valid. The key was to add the client-secret in the trigger like this: shopify app webhook trigger --client-secret=xxxxxxxxxx --api-version 2024-07 --delivery-method http --topic discounts/update --address=https://xxxxx/test-endpoint

Now I get the following log with a valid webhook but an empty admin object:

16:55:41 │                 remix │ test-endpoint_fired Request {
16:55:41 │                 remix │   method: 'POST',
16:55:41 │                 remix │   url: 'http://xxxxx/test-endpoint',
16:55:41 │                 remix │   headers: Headers {
16:55:41 │                 remix │     accept: '*/*',
16:55:41 │                 remix │     'accept-encoding': 'gzip',
16:55:41 │                 remix │     'cdn-loop': 'cloudflare; subreqs=1',
16:55:41 │                 remix │     'cf-connecting-ip': 'XXX',
16:55:41 │                 remix │     'cf-ew-via': '15',
16:55:41 │                 remix │     'cf-ipcountry': 'US',
16:55:41 │                 remix │     'cf-ray': 'XXX',
16:55:41 │                 remix │     'cf-visitor': '{"scheme":"https"}',
16:55:41 │                 remix │     'cf-warp-tag-id': 'XXXX',
16:55:41 │                 remix │     'cf-worker': 'trycloudflare.com',
16:55:41 │                 remix │     connection: 'close',
16:55:41 │                 remix │     'content-length': '227',
16:55:41 │                 remix │     'content-type': 'application/json',
16:55:41 │                 remix │     host: 'XXX.trycloudflare.com',
16:55:41 │                 remix │     'user-agent': 'Shopify-Captain-Hook',
16:55:41 │                 remix │     'x-forwarded-for': 'XXXX',
16:55:41 │                 remix │     'x-forwarded-proto': 'https',
16:55:41 │                 remix │     'x-shopify-api-version': '2024-07',
16:55:41 │                 remix │     'x-shopify-hmac-sha256': 'XXXX',
16:55:41 │                 remix │     'x-shopify-shop-domain': 'shop.myshopify.com',
16:55:41 │                 remix │     'x-shopify-test': 'true',
16:55:41 │                 remix │     'x-shopify-topic': 'discounts/update',
16:55:41 │                 remix │     'x-shopify-triggered-at': '2024-07-25T14:55:40.816021832Z',
16:55:41 │                 remix │     'x-shopify-webhook-id': 'XXXX'
16:55:41 │                 remix │   },
16:55:41 │                 remix │   destination: '',
16:55:41 │                 remix │   referrer: 'about:client',
16:55:41 │                 remix │   referrerPolicy: '',
16:55:41 │                 remix │   mode: 'cors',
16:55:41 │                 remix │   credentials: 'same-origin',
16:55:41 │                 remix │   cache: 'default',
16:55:41 │                 remix │   redirect: 'follow',
16:55:41 │                 remix │   integrity: '',
16:55:41 │                 remix │   keepalive: false,
16:55:41 │                 remix │   isReloadNavigation: false,
16:55:41 │                 remix │   isHistoryNavigation: false,
16:55:41 │                 remix │   signal: AbortSignal { aborted: false }
16:55:41 │                 remix │ }
16:55:41 │                 remix │ [shopify-api/DEBUG] webhook request is valid
16:55:41 │                 remix │ []
16:55:41 │                 remix │ test-endpoint_admin undefined
16:55:41 │                 remix │ test-endpoint_rest {
16:55:41 │                 remix │   topic: 'DISCOUNTS_UPDATE',
16:55:41 │                 remix │   shop: 'shop.myshopify.com',
16:55:41 │                 remix │   session: undefined,
16:55:41 │                 remix │   admin: undefined,
16:55:41 │                 remix │   payload: {
16:55:41 │                 remix │     admin_graphql_api_id: 'gid://shopify/DiscountAutomaticNode/1',
16:55:41 │                 remix │     title: 'Automatic free shipping updated',
16:55:41 │                 remix │     status: 'ACTIVE',
16:55:41 │                 remix │     created_at: '2016-08-29T08:00:00-04:00',
16:55:41 │                 remix │     updated_at: '2016-08-29T08:00:00-04:00'
16:55:41 │                 remix │   }
16:55:41 │                 remix │ }

Another problem worth it to mention and maybe its correlating, when I update the discount code via the developers store with actual saving in the development store. I get a invalid webhook.

I feel like I´m doing something fundamental wrong. So I would be glad about some help from the community.

PS: @lizkenyon & @mohamedhaddi When I start my Project in Debug mode and when I open the remix app in shopify. I get the following log, which let me suspect, that I have a valid session token:

15:31:48 │                 remix │ [shopify-app/INFO] Authenticating admin request
15:31:48 │                 remix │ [shopify-app/DEBUG] Attempting to authenticate session token | {sessionToken:
{"search":"XXXXXXXXXXXX}}
15:31:48 │                 remix │ [shopify-app/DEBUG] Validating session token
15:31:48 │                 remix │ [shopify-app/DEBUG] Session token is valid | {payload:
{"iss":"https://XXX.myshopify.com/admin","dest":"https://XXX.myshopify.com","aud":"XXX
432","sub":"109226852661","exp":XXX,"nbf":XXX,"iat":XXX,"jti":"XXX","sid":"XXX","sig":"XXX"}}
15:31:48 │                 remix │ [shopify-app/DEBUG] Session token is valid | {shop:XXX.myshopify.com, payload: [object
 Object]}
15:31:48 │                 remix │ [shopify-app/DEBUG] Session token is valid | {shop: XXX.myshopify.com, payload: [object
 Object]}
15:31:48 │                 remix │ [shopify-app/DEBUG] Loading session from storage | {sessionId:
offline_XXX.myshopify.com}

Originally posted by @FynnMenkIT in https://github.com/Shopify/shopify-app-template-remix/issues/763#issuecomment-2250622912

lizkenyon commented 3 months ago

How are you creating your webhook subscriptions?

Are you doing it via the config in the shopify.app.toml file or via the UI in the Admin?

FynnMenkIT commented 3 months ago

I am doing it via the UI in the Admin and I am registering it described like in the documentation for v2 https://shopify.dev/docs/api/shopify-app-remix/v2/guide-webhooks. Is there a way for the v2 in the .toml, too ?

lizkenyon commented 3 months ago

I think I know what is going on!

For V2 you must subscribe webhooks as described in that guide by specifying the topics in the shopify.server file.

//shopify.server.ts
const shopify = shopifyApp({
  apiKey: 'abcde1234567890',
  // ...etc
  webhooks: {
    APP_UNINSTALLED: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: '/webhooks',
      // Add more topics here!
    },
  },

When you create webhook subscriptions in the Shopify admin, they are signed with the key that is there. NOT the client secret for your app.

liz-dev-pre-pop-september · Webhooks · Shopify 2024-07-25 11-26-17

Because your app does not have access to that secret key they fail HMAC validation. This is expected.

When you are triggering them with the CLI you are passing that secret key, so they are passing HMAC validation. But they are using the shop domain, shop.myshopify.com. Your app does not have a record of that its database so it cannot return a session. So your admin object is empty.

image

My recommendations: 1) Subscribe to webhooks via the config option in the shopify.server file 2) Then test the webhooks by triggering the actual event, so in your case update the discount code via the developers store, this will ensure that the domain is your test shop!

Please let me know if that solves your issue! 😄

lizkenyon commented 3 months ago

Also, if this is the case, know you are not the first person this has happened to, and we should do better.

I flagged this with my team, and the CLI team so we can best figure out how to stop this confusion from happening with others.

FynnMenkIT commented 3 months ago

@lizkenyon First of all thank you for being so detailed with me 👍

  1. yes I did this the whole time ( I add the whole file so you can have a look at it)
    
    //shopify.server.js
    import "@shopify/shopify-app-remix/adapters/node";
    import {
    AppDistribution,
    DeliveryMethod,
    shopifyApp,
    LATEST_API_VERSION,
    LogSeverity,
    } from "@shopify/shopify-app-remix/server";
    import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
    import { restResources } from "@shopify/shopify-api/rest/admin/2024-01";
    import prisma from "./db.server";

const shopify = shopifyApp({ logger: { level: LogSeverity.Debug, httpRequests: true, }, apiKey: process.env.SHOPIFY_API_KEY, apiSecretKey: process.env.SHOPIFY_API_SECRET || "", apiVersion: "2024-01", scopes: process.env.SCOPES?.split(","), appUrl: process.env.SHOPIFY_APP_URL || "", authPathPrefix: "/auth", sessionStorage: new PrismaSessionStorage(prisma), distribution: AppDistribution.AppStore, restResources, webhooks: { APP_UNINSTALLED: { deliveryMethod: DeliveryMethod.Http, callbackUrl: "/webhooks", }, DISCOUNTS_UPDATE: { deliveryMethod: DeliveryMethod.Http, callbackUrl: "/webhooks", }, }, hooks: { afterAuth: async ({ session }) => { shopify.registerWebhooks({ session }); }, }, future: { v3_webhookAdminContext: true, v3_authenticatePublic: true, unstable_newEmbeddedAuthStrategy: true, }, ...(process.env.SHOP_CUSTOM_DOMAIN ? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] } : {}), });

export default shopify; export const apiVersion = LATEST_API_VERSION; export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders; export const authenticate = shopify.authenticate; export const unauthenticated = shopify.unauthenticated; export const login = shopify.login; export const registerWebhooks = shopify.registerWebhooks; export const sessionStorage = shopify.sessionStorage;

  but in addition I also configured the webhook in the UI ( as far as i understood you right, this is wrong and the configuration in shopify.server.js is enough)

  2. thats what I meant, if I am triggering the actual event and the hook is configured in the UI then I am getting a hmac validation error (thats because of the "false" signing in the Admin UI, **now i get it THX** ). So i dont want to use this anyway.

  Right now if I am following your recommendation and I test it only with a config option in the shopify.server.
  **When I am triggering the actual event in the shop nothing happens.**

   I restarted the project and I also tried to play around with the .env file.

SHOPIFY_APP_URL=XXX-staging.myshopify.com SHOP_CUSTOM_DOMAIN=XXX-staging.myshopify.com SHOPIFY_API_KEY=XXX(Key from the actual staging instance in the shopify partner store) SHOPIFY_API_SECRET=XXX(Key from the actual staging instance in the shopify partner store)


I don`t see what I do wrong here. Still thinking it´s a very simple config error on my project haha :/ 
lizkenyon commented 3 months ago

I would make an API call (with graphiql would be the easiest) to verify that your webhook subscriptions to ensure that your webhook events are being sent to the correct URL.

{
  webhookSubscriptions(first: 5) {
    nodes {
      topic
      endpoint {
        ... on WebhookHttpEndpoint {
          callbackUrl
        }
      }
    }
  }
}

I believe this should do it

If you are using the default functionality of the CLI, your tunnel will change when you restart your server. And this URL will not be updated in your webhook subscription on Shopify. If you reinstall your app, the webhook subscriptions will be recreated with your new URL.

FynnMenkIT commented 3 months ago

Ah this must be the root of all problems thanks @lizkenyon ! So as you said I had wrong URL´s. My URL was like https://maui-estates-beautiful-fire.trycloudflare.com and my GraphQL shows me different URL´s : image

But now after I did your recommendations there is no subscription hook left. How can I register them ?

What I did:

  1. Delete all the Webhooks in the UI
  2. Registering as I in the shopify.server.js ( see my last post)
  3. Deinstalling the APP
  4. shopify app dev --reset where the logs print me 11:18:48 │ webhooks │ APP_UNINSTALLED webhook delivered
  5. double check in the graphQL but now subscription hooks are there image
FynnMenkIT commented 3 months ago

Update

with the help of the issue #462 and in this case I had to use the command shopify app dev --reset because the command npm run dev -- --reset did not work for me

Still it is very confusing behaviour. I have to reinstall my app to even register the webhooks.. And also I have to do this process every time I restart the App or another coworker is working with it..

So my way for a new webhook is like:

  1. add the webhook in the shopify.server.js
  2. reinstall the app
  3. restart the app with the command shopify app dev --reset
  4. double check with graphQL that my webhooks are there

    query shopInfo {
    
    webhookSubscriptions(first: 5) {
    nodes {
      topic
      endpoint {
        ... on WebhookHttpEndpoint {
          callbackUrl
        }
      }
    }
    }
    }

    (5. if the webhooks are not there or wrong registered, restart the process ( I had to do this several times somehow) )

still very huge thx to @lizkenyon, helping another one with the same problem over and over again haha

lizkenyon commented 3 months ago

I know you also mentioned that you were able to get webhooks in the working when registering them in the shopify.app.toml. Part of the reason for that new format was to resolve some of these gotchas. So if your use cases are covered by the new version I would suggest using it. 😄