Community寫作與編輯github.com

khadinakbarlabs/shopify-app-builder

開源 Shopify 應用程式開發外掛,內含 32 項技能,支援 Claude Code、Codex、Cursor、OpenCode、Gemini CLI 及各種 AI 程式碼代理。

shopify-app-builder 是什麼?

這是一個專為 AI 程式碼代理(如 Claude Code、Codex、Cursor、OpenCode 及 Gemini CLI)設計的 Shopify 應用程式開發外掛,內含 32 項技能,涵蓋認證、API 整合、Webhook 處理、資料庫操作等核心功能。其核心文件詳細說明了三種主要的 Shopify 認證流程:Token Exchange(2024 年後推薦)、Managed Installation(適用於無頭應用)及傳統的 OAuth 2.0 授權碼流程,並提供 Remix、Express.js 等框架的完整實作範例。此外,還包括 Prisma、Redis 等多種 Session 儲存介面卡,以及線上/離線權杖的差異說明。開發者可以透過自然語言指令,讓 AI 代理自動產生程式碼、設定認證流程或串接 Shopify API,大幅提升開發效率。

相容平台✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI✓OpenCode
npx skills add khadinakbarlabs/shopify-app-builder

Installed? Explore more 寫作與編輯 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Shopify 應用程式認證

Shopify 提供多種認證流程,取決於您的應用程式類型與使用情境。現代標準為 Token Exchange(2024 年後推出,適用於伺服器渲染應用程式)、Managed Installation(適用於無頭應用程式)以及 OAuth 2.0 授權碼授權(適用於舊版/自訂實作)。所有流程最終都會產生一個用於 Shopify GraphQL Admin API 的存取權杖。

認證流程概覽

1. Token Exchange(2024 年後推薦)

使用情境: 伺服器渲染應用程式(Remix、採用 SSR 的 Next.js)、Shopify CLI 應用程式 流程: 商家安裝應用程式 → Shopify 產生暫時交換權杖 → 應用程式交換取得存取權杖 安全性: 不暴露客戶端密鑰;每次請求使用 PKCE 風格輪替 權杖生命週期: 存取權杖短暫;重新整理權杖會自動輪替

Token Exchange 圖表:

1. 商家在 Shopify 管理後台點擊「安裝」
2. Shopify 重新導向:https://your-app.com/auth/callback?code=EXCHANGE_TOKEN
3. 應用程式驗證 HMAC,將授權碼交換為存取權杖(私密,僅伺服器端)
4. Shopify Admin API 授予權限範圍;權杖儲存在 session/資料庫中
5. 若權杖過期,下次請求時自動重新整理

Remix 實作(Token Exchange):

// shopify.app.ts(應用程式設定)
import { shopifyApp } from '@shopify/shopify-app-remix/server';
import { restResources } from '@shopify/shopify-api/rest/admin/2026-07';
import { PrismaSessionStorage } from '@shopify/shopify-app-session-storage-prisma';
import { prisma } from '~/db.server';

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY || '',
  apiSecret: process.env.SHOPIFY_API_SECRET || '',
  scopes: process.env.SCOPES?.split(',') || [
    'write_products',
    'read_products',
    'write_orders',
    'read_orders',
    'write_customers',
    'read_customers',
    'write_discounts',
    'read_discounts',
    'write_fulfillments',
    'read_fulfillments',
    'write_inventory',
    'read_inventory',
  ],
  appUrl: process.env.SHOPIFY_APP_URL || 'http://localhost:3000',
  auth: {
    path: '/auth',
    callbackPath: '/auth/callback',
  },
  webhooks: {
    path: '/webhooks',
  },
  isEmbeddedApp: true, // Polaris 管理儀表板
  sessionStorage: new PrismaSessionStorage(prisma),
  restResources, // 包含 REST API 輔助工具
});

export default shopify;

環境變數(.env):

SHOPIFY_API_KEY=your-public-api-key-from-partner-dashboard
SHOPIFY_API_SECRET=your-private-api-secret
SHOPIFY_APP_URL=https://your-domain.ngrok.io  # 或正式 URL
SCOPES=write_products,read_products,write_orders,read_orders

認證路由(routes/auth.$.tsx - 捕捉所有路由):

import { redirect } from '@shopify/remix-oxygen';
import { authenticate } from '~/shopify.server';

export const loader = async ({ request }) => {
  const { authenticate } = await import('~/shopify.server');
  return authenticate.admin(request); // Token Exchange 在此發生
};

回呼處理(routes/auth.callback.tsx):

import { json } from '@shopify/remix-oxygen';
import { authenticate } from '~/shopify.server';

export const loader = async ({ request }) => {
  const { session } = await authenticate.admin(request);
  // Session 包含:
  // - session.accessToken(對 API 呼叫有效)
  // - session.shop(商家商店網域)
  // - session.scope(已授予的權限範圍)
  // - session.state(可選的自訂資料)

  return redirect('/app'); // 認證後重新導向至儀表板
};

在 API 呼叫中使用權杖(routes/app.products.tsx):

import { json } from '@shopify/remix-oxygen';
import { authenticate } from '~/shopify.server';
import { GraphQLClient } from 'graphql-request';

export const loader = async ({ request }) => {
  const { session, admin } = await authenticate.admin(request);

  // 方法 1:使用 Shopify 的 admin 輔助工具(推薦)
  const response = await admin.graphql(`
    query GetProducts {
      products(first: 10) {
        edges {
          node {
            id
            title
            handle
          }
        }
      }
    }
  `);

  // 方法 2:手動 GraphQL 呼叫
  const client = new GraphQLClient(
    `https://${session.shop}/admin/api/2026-07/graphql.json`,
    {
      headers: {
        'X-Shopify-Access-Token': session.accessToken,
        'Content-Type': 'application/json',
      },
    }
  );

  const data = await client.request(/* ... */);

  return json({ products: response.data?.products?.edges || [] });
};

權杖重新整理(自動): Token Exchange 權杖會透過 Shopify 的 session 中介軟體自動重新整理。無需手動重新整理:

// 權杖會在每次請求時透明地重新整理
const response = await admin.graphql(query); // 內部處理重新整理

2. Managed Installation(無頭/自訂應用程式)

使用情境: 無頭商店前端、行動應用程式、第三方整合 流程: 商家授權應用程式 → Shopify 產生永久存取權杖(無密鑰輪替) 權杖生命週期: 長存;無需重新整理 安全性: 權杖為永久性;安全儲存在環境變數中

Managed Installation 設定:

在 Shopify 合作夥伴儀表板中:

  1. 應用程式設定 > API 憑證
  2. 在 Admin API 存取範圍下,選擇「Managed installation」
  3. 商家授予一次權限
  4. 將存取權杖複製到您的環境變數
# .env
SHOPIFY_ADMIN_ACCESS_TOKEN=<SHOPIFY_ADMIN_ACCESS_TOKEN>
SHOPIFY_SHOP_URL=example-shop.myshopify.com

使用 Managed Installation 權杖:

import { GraphQLClient } from 'graphql-request';

const client = new GraphQLClient(
  `https://${process.env.SHOPIFY_SHOP_URL}/admin/api/2026-07/graphql.json`,
  {
    headers: {
      'X-Shopify-Access-Token': process.env.SHOPIFY_ADMIN_ACCESS_TOKEN || '',
    },
  }
);

// 權杖永不過期;可隨時呼叫 API
const query = `query { products(first: 10) { edges { node { id title } } } }`;
const products = await client.request(query);

3. OAuth 2.0 授權碼授權(舊版,仍受支援)

使用情境: 採用傳統 OAuth 流程的公開應用程式 流程: 商家點擊「安裝」→ 應用程式重新導向至 Shopify OAuth → 商家授權 → 應用程式收到授權碼 → 應用程式將授權碼交換為權杖 權杖生命週期: 長存存取權杖(除非撤銷,否則不會過期) 安全性: 需要客戶端密鑰;PKCE 為可選

OAuth 流程圖表:

1. 商家造訪:https://your-app.com/auth
2. 應用程式重新導向至:https://your-shop.myshopify.com/admin/oauth/authorize?client_id=KEY&scope=write_products&redirect_uri=https://your-app.com/auth/callback&state=RANDOM
3. 商家在 Shopify 管理後台授權應用程式
4. Shopify 重新導向回:https://your-app.com/auth/callback?code=AUTHORIZATION_CODE&hmac=SIGNATURE&state=RANDOM&shop=your-shop.myshopify.com
5. 應用程式驗證 HMAC 和 state
6. 應用程式將授權碼交換為權杖(伺服器端,使用客戶端密鑰)
7. 應用程式將權杖儲存在資料庫中

Express.js OAuth 範例:

import express from 'express';
import axios from 'axios';
import crypto from 'crypto';

const app = express();

const API_KEY = process.env.SHOPIFY_API_KEY || '';
const API_SECRET = process.env.SHOPIFY_API_SECRET || '';
const REDIRECT_URI = process.env.REDIRECT_URI || 'https://your-app.com/auth/callback';
const SCOPES = 'write_products,read_products';

// 步驟 1:將商家重新導向至 Shopify OAuth
app.get('/auth', (req, res) => {
  const shop = req.query.shop as string;

  if (!shop || !shop.includes('.myshopify.com')) {
    return res.status(400).send('缺少或無效的 shop 參數');
  }

  const state = crypto.randomBytes(16).toString('hex');
  const nonce = crypto.randomBytes(16).toString('hex');

  // 將 state 儲存在 session 中(或資料庫)以供驗證
  req.session.state = state;
  req.session.nonce = nonce;

  const authUrl = new URL(
    `/admin/oauth/authorize`,
    `https://${shop}`
  );
  authUrl.searchParams.append('client_id', API_KEY);
  authUrl.searchParams.append('scope', SCOPES);
  authUrl.searchParams.append('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.append('state', state);

  res.redirect(authUrl.toString());
});

// 步驟 2:處理 OAuth 回呼
app.get('/auth/callback', async (req, res) => {
  const { code, hmac, shop, state } = req.query;

  // 驗證 HMAC
  const message = Object.entries(req.query)
    .filter(([key]) => key !== 'hmac')
    .map(([key, value]) => `${key}=${value}`)
    .sort()
    .join('&');

  const hash = crypto
    .createHmac('sha256', API_SECRET)
    .update(message, 'utf8')
    .digest('base64');

  if (hash !== hmac) {
    return res.status(401).send('偵測到未經授權的請求');
  }

  // 驗證 state
  if (state !== req.session.state) {
    return res.status(401).send('State 不匹配');
  }

  try {
    // 將授權碼交換為存取權杖
    const response = await axios.post(
      `https://${shop}/admin/oauth/access_token`,
      {
        client_id: API_KEY,
        client_secret: API_SECRET,
        code,
      }
    );

    const { access_token, scope } = response.data;

    // 儲存存取權杖(儲存在資料庫,而非 session,以確保持久性)
    await storeAccessToken(shop as string, access_token, scope);

    // 重新導向至應用程式儀表板
    res.redirect(`/app?shop=${shop}`);
  } catch (error) {
    console.error('權杖交換錯誤:', error);
    res.status(500).send('認證失敗');
  }
});

// 輔助函數以儲存權杖
async function storeAccessToken(shop: string, token: string, scope: string) {
  // 儲存在資料庫中(使用模擬儲存為範例)
  const db = {
    shops: {} as Record<string, { token: string; scope: string }>,
  };
  db.shops[shop] = { token, scope };
}

app.listen(3000);

Session 儲存介面卡

存取權杖必須持久儲存。Shopify 為常見的儲存後端提供了介面卡:

Prisma(適用於 Remix 的推薦方案)

Schema(prisma/schema.prisma):

model Session {
  id        String    @id
  shop      String
  state     String
  isOnline  Boolean   @default(false)
  accessToken String
  refreshToken String?
  scope     String
  expiresAt DateTime?
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  @@unique([shop, state])
  @@index([shop])
}

設定(shopify.app.ts):

import { PrismaSessionStorage } from '@shopify/shopify-app-session-storage-prisma';
import { prisma } from '~/db.server';

const shopify = shopifyApp({
  // ...
  sessionStorage: new PrismaSessionStorage(prisma),
});

Redis

import { RedisSessionStorage } from '@shopify/shopify-app-session-storage-redis';
import redis from 'redis';

const redisClient = redis.createClient({
  host: 'localhost',
  port: 6379,
});

const sessionStorage = new RedisSessionStorage({
  client: redisClient,
  prefix: 'shopify_session:',
});

const shopify = shopifyApp({
  // ...
  sessionStorage,
});

記憶體中(僅限開發環境)

import { MemorySessionStorage } from '@shopify/shopify-app-session-storage';

const sessionStorage = new MemorySessionStorage();

const shopify = shopifyApp({
  // ...
  sessionStorage, // 注意:應用程式重新啟動時 Session 會遺失;僅限開發環境
});

DynamoDB

import { DynamoDBSessionStorage } from '@shopify/shopify-app-session-storage-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

const dynamoDBClient = new DynamoDBClient({ region: 'us-east-1' });

const sessionStorage = new DynamoDBSessionStorage({
  client: dynamoDBClient,
  tableName: 'shopify-sessions',
});

const shopify = shopifyApp({
  // ...
  sessionStorage,
});

線上 vs. 離線權杖

線上權杖:

  • 範圍:目前使用者的權限(通常是管理員使用者)
  • 到期時間:24 小時
  • 使用情境:基於瀏覽器的操作(Polaris 管理儀表板)
  • 限制:無法執行背景任務;使用者登出時受限

離線權杖:

  • 範圍:應用程式已授予的權限範圍
  • 到期時間:無;除非撤銷否則永久有效
  • 使用情境:背景任務、Webhook、排程任務
  • 限制:無;預設用於應用程式操作

在 Token Exchange 中請求離線權杖:

// shopify.app.ts
const shopify = shopifyApp({
  // ...
  auth: {
    path: '/auth',
    callbackPath: '/auth/callback',
  },
});

// Remix 預設會自動請求離線權杖
// 無需動作;請使用 session.accessToken 進行 API 呼叫

將離線權杖用於 Webhook:

// webhooks/products-update.ts
export const webhooks = {
  APP_UNINSTALLED: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: '/webhooks/app-uninstalled',
  },
  PRODUCTS_UPDATE: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: '/webhooks/products-update',
  },
};

export default defineWebhooksConfig(
  async (request, { admin, session }) => {
    const { body, query } = await graphql.query(request, {
      query: GET_PRODUCT,
      variables: { id: 'gid://shopify/Product/123' },
    });

    // session.accessToken 是離線權杖;此處有效
    console.log(`已為商店 ${session.shop} 使用權杖處理 Webhook`);
  },
  webhooksConfig
);

應用程式代理認證

使用情境: 商店前端(對外公開)對應用程式後端的請求 認證方式: HMAC 簽章驗證(類似 Webhook) 流程: 商店前端 → Liquid 代理請求 → 應用程式後端(驗證 HMAC)→ 回應

設定應用程式代理(shopify.app.toml):

[[extensions]]
type = "app_proxy"
name = "Storefront API"
url = "/api/proxy"
subpath = "loyalty"  # 對 /apps/loyalty/* 的請求會路由至此

應用程式代理處理常式(Remix routes/api/proxy.ts):

import { json } from '@shopify/remix-oxygen';
import crypto from 'crypto';

export const loader = async ({ request }) => {
  const url = new URL(request.url);
  const hmac = url.searchParams.get('hmac') || '';
  const timestamp = url.searchParams.get('_t') || '';
  const shop = url.searchParams.get('shop') || '';

  // 驗證 HMAC
  // ...(因原文截斷,此為示意)

  return json({ message: '成功' });
};

最佳實踐

  • 始終使用 Token Exchange 用於新應用程式。它更安全,且無需管理客戶端密鑰。
  • 對背景任務使用離線權杖。它們永久有效,直到被撤銷。
  • 將權杖安全儲存在資料庫或安全環境變數中。永遠不要暴露給客戶端。
  • 驗證所有回呼中的 HMAC 簽章,以防止 CSRF 和中間人攻擊。
  • 使權杖範圍盡可能小,以限制資料庫暴露。

結論

Shopify 的認證流程旨在滿足從簡單安裝到複雜多層應用程式的各種用例。對於新開發,2024 年的 Token Exchange 流程是首選方法,其次是 Managed Installation 用於無頭應用程式。了解這些認證模式對於建置安全且可擴展的 Shopify 應用程式至關重要。

相關技能