Summary
This postmortem analyzes why a Shopify app fails the Shopify App Store Review Test for “using session token for user authentication.” The issue is common among new Shopify app developers who misunderstand how Shopify’s session tokens differ from traditional server‑side authentication. The failure stems from an incorrect or incomplete implementation of Shopify’s required JWT-based session token flow.
Root Cause
The core problem is that the app is treating Shopify session tokens like persistent user authentication, which violates Shopify’s security model.
Key root causes include:
- Using session tokens as long-lived authentication instead of short-lived, per-request verification.
- Not verifying the JWT on every request to the backend.
- Relying on cookies or server sessions, which Shopify explicitly forbids for embedded apps.
- Incorrect or missing middleware for validating Shopify session tokens.
- Frontend not refreshing tokens before calling backend APIs.
- Backend endpoints accepting requests without token validation.
Why This Happens in Real Systems
This failure is extremely common because:
- Developers assume Shopify behaves like a normal OAuth app.
- JWT session tokens feel similar to “login tokens,” but Shopify uses them differently.
- Many tutorials online are outdated or incomplete.
- Shopify’s embedded app model requires stateless authentication, which is unfamiliar to many developers.
- Developers often mix OAuth installation flow with user authentication flow.
Real-World Impact
If not fixed, the app will:
- Fail Shopify App Store review every time.
- Be rejected for security vulnerabilities.
- Break when merchants use the app inside the Shopify Admin.
- Expose backend endpoints to unauthorized access.
- Cause intermittent “Not authorized” errors for real merchants.
Example or Code (if necessary and relevant)
Below is a minimal example of a correct backend token verification using Shopify’s recommended middleware.
import { shopifyApp } from "@shopify/shopify-app-express";
const shopify = shopifyApp({
api: { ... },
auth: { ... },
});
app.use("/api/*", shopify.validateAuthenticatedSession());
And a correct frontend call:
const token = await shopify.sessionToken.get();
await fetch("/api/data", {
headers: { Authorization: `Bearer ${token}` },
});
How Senior Engineers Fix It
Experienced engineers resolve this by enforcing strict, stateless, per-request authentication:
- Validate the JWT on every backend request using Shopify’s middleware.
- Never store session tokens in cookies, localStorage, or server sessions.
- Use OAuth only for installation, not for user authentication.
- Use sessionToken.get() before every API call from the frontend.
- Ensure backend rejects any request without a valid token.
- Implement proper error handling so the frontend refreshes tokens when needed.
They also test the app using:
- Shopify Admin embedded environment
- Incognito windows
- Multiple stores
- Expired tokens
- Backend endpoints hit directly (to ensure they reject unauthenticated requests)
Why Juniors Miss It
Junior developers often miss this because:
- They assume OAuth = authentication, which is incorrect in Shopify.
- They treat session tokens like normal login tokens.
- They rely on outdated tutorials or StackOverflow answers.
- They don’t test backend endpoints without the frontend.
- They don’t fully understand JWT validation or stateless auth.
- Shopify’s documentation is dense, and the distinction between installation and authentication is easy to overlook.
The key takeaway:
Shopify requires stateless, per-request JWT validation, and any deviation from this will fail the review.
If you want, I can review your repo structure and point out exactly where the authentication flow breaks.