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-packThis skill is limited to using the following tools:
Security essentials for Shopify apps: credential management, webhook HMAC validation, request verification, and least-privilege access scopes.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
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.