Contents
Shopify App Security – Vulnerabilities Every Developer Should Check For
Embedded apps are the default way to build on Shopify today. They live inside an iframe in the Shopify admin, authenticate with session tokens, and react to webhooks. That architecture is convenient, and it introduces an attack surface that is easy to get wrong. Unlike a standalone web app, an embedded app is trusting input from three directions at once: the merchant, the Shopify platform, and the end customer. Each boundary is a place where missing checks becomes a threat to Shopify app security.
This post walks through the Shopify Shopify app security and common vulnerability classes that actually show up in Shopify embedded apps. You will also learn how to detect each in your own code, and how to close it. None of this is exotic. Most shopify app security hacks happen simply because a developer skipped a basic check that Shopify already provides tools for.
Shopify App Security To Keep a Check On For a Secured Embeded App
1. Unverified session tokens – Embedded apps authenticate requests using session tokens (JWTs) issued through App Bridge. The frontend attaches the token to each request; your backend is supposed to verify it. The common mistake is simple: developers read the shop domain from the token without checking its signature, which means they trust a value that the client could easily fake.
Every incoming request must have its JWT validated against your app’s API secret, including signature and expiry, before you act on it. In practice this means calling a real verify function that checks the signature with your secret and throws on an invalid or expired token, and only then trusting the shop value from the token. The common mistake is using a decode function that merely reads the payload withou checking the signature.
Audit check: Search your codebase for anywhere you decode a JWT. If you are decoding the token without verifying its signature, that is the bug — switch to a verifying call that validates the signature and expiry.
2. Unverified webhooks – Shopify signs every webhook with an HMAC-SHA256 digest sent in a request header. If you do not verify it, your webhook endpoint will happily process any POST that reaches the URL: fake order-created events, fake ‘app uninstalled’ events that wipe out a merchant’s data, and messed-up analytics figures.
Verify the HMAC against the raw request body, not the parsed JSON, because re-serializing changes the bytes and breaks the digest. Recompute the HMAC-SHA256 of the raw body using your app secret, then compare it to the value in the header using a timing-safe comparison rather than a plain equality check.
Audit check: Confirm your framework is not parsing the body before you compute the HMAC. In Express, you need the raw body parser on the webhook route, not the global JSON parser.
3. OAuth flow: missing state and open redirects – The install/OAuth flow is a classic CSRF target. Before redirecting a merchant to the Shopify authorization screen, you generate a state nonce; when Shopify redirects back, you must confirm the returned state matches what you issued. Skip it, and an attacker can stitch a merchant’s session to tokens they control.
Two more Shopify app security checks belong in the same flow: validate the hmac query parameter Shopify includes on the callback, and confirm the shop parameter matches the pattern for a myshopify.com domain before using it anywhere. In short, on the callback you should compare the returned state against the one you stored and reject the request on mismatch, and you should validate the shop domain against a strict pattern before using it in a redirect or a database query.
Audit check: Trace your callback handler. If state is generated but never compared, or shop flows into a redirect or DB query without a format check, fix both.
4. Weak Content Security Policy and clickjacking – Your app renders inside the admin’s iframe, so you must allow framing, but only by Shopify. Get the frame-ancestors directive wrong and either your app will not load, or worse, it can be embedded by a malicious page for clickjacking. Set the directive dynamically so that only the requesting shop and the Shopify admin are allowed to frame your app.
Never use a wildcard that allows any site to frame your app, and do not fall back to the legacy header that allows all framing. Lock framing to the specific shop and Shopify admin.
Audit check: Check the CSP header your app actually sends in production, not just what’s written in your config. Make sure it is set for each specific shop.
5. Stored XSS / code injection through merchant and customer input – This is what most people mean when they say “inject code into the app.” If your app stores input, such as a product label, a custom field, a button text, or a note, and later renders it into the admin or storefront without escaping an attacker can inject code that runs in that context, like an image or iframe tag built to trigger a script, or a plain script tag.
To prevent this, always escape untrusted data before displaying it in HTML, based on the context where it appears. In React, show values as normal text, because JSX escapes them automatically. Treat any feature that injects raw HTML as a warning sign that the data must first be cleaned using a library like DOMPurify. In server-side templates, use the template engine’s built-in escaping syntax instead of inserting values directly. Sanitize or reject input at the boundary too, but never rely on input filtering alone — output encoding is the control that actually stops execution.
Audit check: Search for any raw-HTML rendering APIs and any template that outputs stored fields without escaping. Each one is a candidate for stored XSS.
6. IDOR: not scoping queries by shop – A multi-tenant app holds many shops’ data in shared tables. If a query is scoped only by a record ID and not by the authenticated shop, shop A can read or modify shop B’s data just by changing an ID in the request. This is an Insecure Direct Object Reference, and it is one of the most damaging because it is invisible until exploited.
Every query that accesses a tenant data must include the shop from the verified session token as a filter. A query that selects a record by its ID alone is vulnerable, because any ID from any shop will resolve. The safe version adds a condition that the record also belongs to the authenticated shop, using the shop value taken from the verified session rather than from the request.
Audit check: Review every data-access query. If the tenant identifier comes from the request body or params rather than the verified session, or is not in the filter at all, that is an IDOR.
7. Leaked secrets and tokens – Access tokens and API secrets often end up in places they shouldn’t: saved in code repositories, printed in error logs, or included in frontend JavaScript. If a shop’s offline access token leaks, an attacker gets access to that shop’s API until the token is changed.
Keep secrets in environment variables or a secrets manager, never in client-side code. Remove tokens from logs and from error messages sent back to clients. Showing a raw error message directly to the user is an easy way to leak internal details. Request the minimum OAuth scopes your app needs, so the blast radius is smaller if a token is compromised.
Audit check: Run a secret scanner over your git history, and confirm your frontend bundle contains no access tokens.

How the Shopify App Security get exploited (attacker’s mindset)
Understanding how an attacker thinks helps you prioritize the checks above correctly. This is the mental model, not a playbook: the point is to recognize which weaknesses are most valuable to an attacker, and therefore most urgent to close.
- Forged requests — an attacker checks whether your endpoints really verify the session token or just read the shop from it. The fix is to verify the token on every request. The lesson: just because it works in testing doesn’t mean it’s safe.
- Fake webhooks — attackers look for webhook URLs that respond the same whether or not the signature is valid. Confirm yours rejects unsigned or tampered calls outright.
- Data crossing tenants (IDOR) — The common trick is to change an identifier and see whose data comes back. That’s why every query must include the verified shop in its filter.
- Stored XSS — attacker-controlled input saved into a stored field is the highest-value target, because it can run in the admin or storefront later. Treat every place you render stored data as untrusted.
A pre-launch Shopify app security checklist
Before shipping, verify each of these:
- Every API request validates the session token signature and expiry.
- Every webhook verifies the HMAC against the raw body with a timing-safe comparison.
- OAuth validates the state value, the hmac parameter, and the shop domain format.
- The CSP frame-ancestors directive is scoped to the shop and the Shopify admin.
- All stored input is output-encoded on render, with no unsanitized raw-HTML injection.
- Every tenant query is filtered by the verified shop.
- No secrets in client code, logs, or error responses; minimum scopes requested; tokens rotated when the app is uninstalled.
- Shopify app security is not about one clever defense — it is about not skipping the checks the platform already provides. Session tokens, webhook signatures, and the OAuth state value are all there for you; the shopify app vulnerabilities come from treating them as optional.
Audit against the list above, and you have closed the paths that account for the overwhelming majority of real-world embedded-app compromises bringing a threat to Shopify app security.
Author – Amit Porwal – Founder WISER AI