How Can an Unpublished Shopify App Be Installed by Unauthorized Stores?

Question: How Can an Unpublished Shopify App Be Installed by Unauthorized Stores?

I am currently testing a Shopify app that is not published on the Shopify App Store and is only deployed on a hosting platform (Railway).

My understanding was that during the testing phase, only my development/testing store should be able to install the app. However, I observed that unauthorized stores were able to install it, which was unexpected.


Clarification Needed

How is it possible for external or unknown Shopify stores to install my app when:

  • The app is not listed on the Shopify App Store

  • It is still in the private/testing phase

  • It is only deployed via a backend URL (not publicly promoted)


Observation

It appears that installations may be happening through:

  • Direct OAuth URLs (/api/auth?shop=...)

  • Shopify Partners Dashboard install flow


Question

Is it expected behavior that any Shopify store can initiate installation via OAuth if they have the app URL, even if the app is not published?

If so:

  • What is the recommended way to strictly restrict installation to only specific stores during testing?

  • Are there any built-in Shopify mechanisms for limiting installs, or is this entirely the developer’s responsibility?


Goal

I want to ensure that no unauthorized store can install or access the app until it is officially published.

Any clarification or best practices would be appreciated.

1 Like

Hi @Ali_Hamza,

This might be expected behaviour depending on a number of different variables, such as the status of the app (draft, unlisted, etc), the distribution method selected for the app (custom or public), what installation method is used (shopify managed installation or authentication code grant installation), etc.

For example, if the app is Public distribution app, and has gone through the app review process, and is Unlisted. It is still possible for any store to install the app if they have the app url, since Unlisted just means that the App Store Listing for the app is not listed publically and is not searchable on the app store or crawlable by web crawlers, but the app store page for the app does still exists and is used for installation by you giving the App Store Listing URL to any merchants you do want install the app.

With Custom distribution apps, when selecting the distribution method, you have to add a store url, which restricts the app installation to only that store, or plus org associated with that store. So unauthorized stores shouldn’t be able to install Custom distribution apps without you allowing it.

Then looking at the installation method, with Shopify Managed Installations, we handle the authentication and verification during the install process automatically, but if you’re not using Shopify Managed Installations, and are using the Authorization Code Grant installation method, you do need to verify the install requests within your app code, as described in the Shopify.dev docs.

Here’s some documentation on the topics I mentioned above:

If you have a specific app and store that installed it unexpectedly, that you’d like us to help look into further, we can definitely do that, though we will need you to reach out via the Shopify Help Center while logged into the Partner account that owns the app, and we can help look into this in more detail, in a fully authenticated support interaction.

Hi, thank you for the detailed explanation — it really helped me understand the expected behavior more clearly.

Based on your response, I now understand that since my app is using the Authorization Code Grant flow (manual OAuth), Shopify is responsible for validating the authenticity of the request (HMAC, state, etc.), but restricting which stores are allowed to install the app is entirely the responsibility of my app logic.

In my case, the app is currently a public (draft) app and deployed on a hosting platform, not yet published on the App Store. I initially assumed that during this testing phase, installation would be limited to my development store, but I now understand that this is not enforced automatically.

I also identified and fixed a gap in my implementation where the /api/auth/callback route was not protected, which allowed unauthorized installations via OAuth bypass. I have now implemented strict allowlist validation across all entry points (OAuth, API routes, public routes, and billing callbacks), along with a hard fail-safe after OAuth and cleanup of unauthorized sessions.

I have a few clarifications:

  1. When I try to install the app on another store manually, the install button appears disabled. However, another store was still able to install the app. Could you clarify how installation is still possible in this case? Is this due to installation via the Partners Dashboard or direct OAuth URL bypassing the UI restriction?

  2. Since my app is currently a public draft app, is it expected that any store with access to the OAuth URL can initiate installation, regardless of App Store visibility?

  3. To strictly restrict installation during testing, would using Custom App Distribution be the recommended approach, or is implementing server-side allowlisting (as I’ve done) considered sufficient and aligned with Shopify best practices?

Appreciate your guidance — this has helped me better understand the separation between Shopify’s authentication layer and application-level authorization.

Hi @Ali_Hamza,

I’ve looked into this further, replicating the behaviour with my own test apps and partner accounts, and I’ve discussed this further with our developers internally to confirm the expected behaviour here.

We can confirm that it is expected that an unrelated partner development store may be able to install an Undecided or Public (including draft) distributed app, as long as they have the App’s Client ID.

The install can occur with the oauth/install path, as you’ve mentioned, with the app’s client id like so:

  • https://admin.shopify.com/store/<store_name>/oauth/install?client_id=<client_id>

There are a couple different ways you can prevent this.

  1. Use the “legacy install flow” when creating the app via the Dev Dashboard


    This makes the app installation use the Authentication Code Grant install method, and does require the app itself to redirect the user to the install page in the admin, as described in Step 1 and 2 of the docs.

  2. Create the app as a Custom distribution method while developing it, that way you can specify what store or Plus Org is able to install it when selecting the distribution method. Then when you are ready to submit the app for app review, you could recreate the app in the Dev Dashboard and select the Public distribution method and submit the app for review.

  3. If the app is undecided or Public, and using Shopify Managed Installation (if “use legacy install flow” is unselected when creating via the Dev Dashboard), there’s no way of preventing this before the installation as Shopify automatically redirects the user to the install page if they use the oauth/install URL with the Client ID, but you can add additional logic to your app code to handle unauthorized sessions as you’ve mentioned, after the app is installed.

    If you haven’t already, I’d recommend using the appUninstall mutation to uninstall the app from any unauthorized stores.

Additionally, to prevent this from happening in the future, be sure to keep your app’s Client ID secure and don’t share it or post it anywhere publicly.