shopify-security-basics
Installation
SKILL.md
Shopify Security Basics
Overview
Security essentials for Shopify apps: credential management, webhook HMAC validation, request verification, and least-privilege access scopes.
Prerequisites
- Shopify Partner account with app credentials
- Understanding of HMAC-SHA256 signatures
- Access to Shopify app configuration
Instructions
Step 1: Secure Credential Storage
# .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 |
Step 2: Webhook HMAC Verification
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");
}
);
Step 3: OAuth Request Verification
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)
);
}
Step 4: Minimal Access Scopes
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"
Step 5: Content Security Policy for Embedded Apps
// 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();
});
Output
- Credentials securely stored in environment variables
- Webhook HMAC verification on all incoming webhooks
- OAuth request signatures validated
- Minimal access scopes configured
- CSP headers set for embedded apps
Error Handling
| 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 |
Examples
Security Audit Checklist
- Access tokens in environment variables, never in code
-
.envfiles in.gitignore - Webhook HMAC verified on every incoming webhook
- OAuth HMAC verified on app installation requests
- Minimal scopes — only what the app needs
- CSP
frame-ancestorsset for embedded apps - No admin tokens in client-side JavaScript
- Token rotation procedure documented
-
git-secretsor similar pre-commit hook installed
Install git-secrets to Prevent Token Leaks
# 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
Resources
- Shopify Webhook HMAC Verification
- Shopify API Authentication
- Access Scopes Reference
- Embedded App Security
Next Steps
For production deployment, see shopify-prod-checklist.
Weekly Installs
1
Repository
jeremylongshore…s-skillsGitHub Stars
2.1K
First Seen
Mar 26, 2026
Security Audits