Skip to main content

Authenticate every request

Authenticate every request before you read or write store data, and take the store from the result of that authentication, never from the request itself.


Your shipped JavaScript, including theme app extension assets and embedded app bundles, names every endpoint that it calls, so every endpoint is discoverable. An endpoint that accepts the store from a shop parameter or body field lets any caller act as any store that has your app installed. Endpoints that save settings your storefront code renders, such as widget settings or custom CSS, JavaScript, or HTML, are the highest-impact case, because one request puts a script on a storefront.

Expiring access tokens and API log reviews don't detect this. Your own server makes the follow-up GraphQL Admin API call with a valid token, so the request looks like normal traffic.


Match the authentication method to where the request comes from, then reject anything that doesn't verify.

Anchor to Match the method to the requestMatch the method to the request

Request comes fromAuthenticate it withIn the React Router app template
Your embedded app in the Shopify adminID tokenauthenticate.admin
A storefront, including JavaScript in your theme app extensionApp proxy signatureauthenticate.public.appProxy
A checkout or customer account UI extensionThe extension's session tokenauthenticate.public.checkout or authenticate.public.customerAccount
A Shopify webhookHMAC signatureauthenticate.webhook

Anchor to Reject what doesn't verifyReject what doesn't verify

  • If a request has no valid token or signature, then reject it before doing anything else. Don't fall back to a shop value from a parameter, body field, header, or cookie.
  • An app proxy signature proves which store a request came through, not who sent it, so don't let app proxy routes change store settings. For actions on behalf of a logged-in customer, use the logged_in_customer_id value from the signed request.
  • A request that your storefront JavaScript sends directly to your server, rather than through an app proxy, carries no proof of which store it came from. Treat it as anonymous: it can read public data, but it must not change anything for a store.
  • Debug, test, migration, and legacy routes need the same authentication. If you don't need a route, then remove it.

Anchor to If you don't use a Shopify libraryIf you don't use a Shopify library

  • Validate every part of the ID token: signature, expiry, audience, and destination claims.
  • Verify webhook HMACs against the raw request body before parsing, and treat webhook headers, including the topic and shop domain, as untrusted until the signature verifies. Make webhook handling idempotent, and use delivery IDs to detect replays.
  • Bind the OAuth state value to the browser that started the flow, and check it on return. Require exact matches against your approved redirect URLs.
  • Validate a shop domain against a pattern that's anchored at both ends, so that a value such as example.myshopify.com.attacker.net doesn't match.

For details, see authentication and authorization and verify webhook deliveries.


Was this page helpful?