Help us improve
Share bugs, ideas, or general feedback.
From shopify-pack
Implements Shopify security best practices for API credential storage, webhook HMAC validation with TypeScript/Express, and access scopes.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin shopify-packHow this skill is triggered — by the user, by Claude, or both
Slash command
/shopify-pack:shopify-security-basicsThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Security essentials for Shopify apps: credential management, webhook HMAC validation, request verification, and least-privilege access scopes.
Secures Shopify apps via HMAC webhook verification, session token validation, OAuth scope checks, CSP headers, GDPR webhooks, and input sanitization.
Provides patterns for Shopify app development using Remix/React Router, embedded apps with App Bridge, webhooks, GraphQL Admin API, Polaris components, billing, and extensions.
Installs and configures Shopify app authentication with OAuth, session tokens, and @shopify/shopify-api SDK for API access in Node.js apps.
Share bugs, ideas, or general feedback.
Security essentials for Shopify apps: credential management, webhook HMAC validation, request verification, and least-privilege access scopes.
# .env — NEVER commit
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret_key
SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# .gitignore — add immediately
.env
.env.local
.env.*.local
*.pem
Token format reference:
| Token Type | Prefix | Length | Used For |
|---|---|---|---|
| Admin API access token | shpat_ | 38 chars | Server-side Admin API |
| Storefront API token | varies | varies | Client-safe storefront queries |
| API secret key | none | 32+ hex | Webhook HMAC, OAuth |
Shopify signs every webhook with your app's API secret using HMAC-SHA256. The signature is in the X-Shopify-Hmac-Sha256 header.
import crypto from "crypto";
import express from "express";
function verifyShopifyWebhook(
rawBody: Buffer,
hmacHeader: string,
secret: string
): boolean {
const computed = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("base64");
// Timing-safe comparison prevents timing attacks
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(hmacHeader)
);
}
// Express middleware — MUST use raw body parser
app.post(
"/webhooks",
express.raw({ type: "application/json" }),
(req, res) => {
const hmac = req.headers["x-shopify-hmac-sha256"] as string;
const topic = req.headers["x-shopify-topic"] as string;
const shop = req.headers["x-shopify-shop-domain"] as string;
if (!verifyShopifyWebhook(req.body, hmac, process.env.SHOPIFY_API_SECRET!)) {
console.warn(`Invalid webhook HMAC from ${shop}, topic: ${topic}`);
return res.status(401).send("HMAC validation failed");
}
const payload = JSON.parse(req.body.toString());
console.log(`Verified webhook: ${topic} from ${shop}`);
// Process asynchronously — respond 200 within 5 seconds
processWebhookAsync(topic, shop, payload);
res.status(200).send("OK");
}
);
Verify that incoming requests from Shopify are authentic by checking the HMAC query parameter:
import { shopifyApi } from "@shopify/shopify-api";
// The library handles this automatically, but here's the manual approach:
function verifyShopifyRequest(query: Record<string, string>, secret: string): boolean {
const { hmac, ...params } = query;
if (!hmac) return false;
// Sort parameters and create query string
const message = Object.keys(params)
.sort()
.map((key) => `${key}=${params[key]}`)
.join("&");
const computed = crypto
.createHmac("sha256", secret)
.update(message)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(hmac)
);
}
Only request the scopes your app actually needs:
| Use Case | Required Scopes |
|---|---|
| Read-only product catalog | read_products |
| Product management | read_products, write_products |
| Order dashboard | read_orders |
| Fulfillment automation | read_orders, write_fulfillments, read_fulfillments |
| Customer loyalty app | read_customers, write_customers |
| Full admin app | Request scopes incrementally, not all at once |
# shopify.app.toml — start minimal, add as needed
[access_scopes]
scopes = "read_products"
# Use optional scopes for features that not all merchants need
[access_scopes.optional]
scopes = "write_products,read_orders"
// Embedded apps must set proper CSP headers
app.use((req, res, next) => {
const shop = req.query.shop as string;
res.setHeader(
"Content-Security-Policy",
`frame-ancestors https://${shop} https://admin.shopify.com;`
);
next();
});
| Security Issue | Detection | Mitigation |
|---|---|---|
| Token in git history | git log -p | grep shpat_ | Rotate token immediately, use git-secrets |
| Invalid webhook HMAC | 401 responses in webhook handler | Verify API secret matches Partner Dashboard |
| Missing scope | 403 errors on API calls | Add scope to shopify.app.toml and re-auth |
| Token exposed in client JS | Browser devtools | Never send admin tokens to the browser |
.env files in .gitignoreframe-ancestors set for embedded appsgit-secrets or similar pre-commit hook installed# Install git-secrets
brew install git-secrets # macOS
# or: sudo apt install git-secrets # Linux
# Add Shopify patterns
git secrets --add 'shpat_[a-f0-9]{32}'
git secrets --add 'shpss_[a-f0-9]{32}'
# Install hook
git secrets --install
For production deployment, see shopify-prod-checklist.