CommunitySchreiben & Editierengithub.com

khadinakbarlabs/shopify-app-builder

Open-Source Shopify-App-Entwicklungsplugin mit 32 Fähigkeiten für Claude Code, Codex, Cursor, OpenCode, Gemini CLI und KI-Codierungsagenten.

Was ist shopify-app-builder?

Dieses Skill-Paket bietet eine umfassende Anleitung zur Shopify-App-Authentifizierung und tokenbasierten API-Integration, die für KI-Codierungsagenten auf Plattformen wie Claude Code, Cursor, Codex, OpenCode und Gemini CLI optimiert ist. Es deckt drei Authentifizierungsflüsse ab: Token Exchange (empfohlen ab 2024) für serverseitig gerenderte Apps, Managed Installation für Headless-Apps und den Legacy-OAuth-2.0-Autorisierungscode-Grant. Der Skill enthält vollständige Implementierungsbeispiele in TypeScript für Remix und Express.js, einschließlich Routendefinitionen, HMAC-Validierung, Token-Speicherung mit Adaptern (Prisma, Redis, DynamoDB, In-Memory) und die Unterscheidung zwischen Online- und Offline-Tokens für Hintergrundjobs und Webhooks. Entwickler profitieren von vorgefertigten Codebausteinen, die die Shopify-App-Entwicklung vereinfachen, Sicherheitsvorkehrungen automatisieren und die Konfiguration für mehr als 30 Shopify-API-Berechtigungen optimieren.

Funktioniert mit✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI✓OpenCode
npx skills add khadinakbarlabs/shopify-app-builder

Installed? Explore more Schreiben & Editieren skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Shopify-App-Authentifizierung

Shopify bietet mehrere Authentifizierungsabläufe, abhängig von Ihrem App-Typ und Anwendungsfall. Der moderne Standard ist der Token Exchange (2024+) für serverseitig gerenderte Apps, die Managed Installation für Headless-Apps und der OAuth 2.0 Authorization Code Grant für Legacy-/benutzerdefinierte Implementierungen. Alle Abläufe führen zu einem Zugriffstoken für die Shopify GraphQL Admin API.

Übersicht über Authentifizierungsabläufe

1. Token Exchange (Empfohlen ab 2024)

Anwendungsfall: Serverseitig gerenderte Apps (Remix, Next.js mit SSR), Shopify CLI Apps Ablauf: Händler installiert App → Shopify generiert temporäres Exchange-Token → App tauscht es gegen Zugriffstoken Sicherheit: Kein Client-Secret offengelegt; verwendet PKCE-ähnliche Rotation pro Anfrage Token-Lebensdauer: Zugriffstoken sind kurzlebig; Refresh-Token rotieren automatisch

Token Exchange Diagramm:

1. Händler klickt auf "Installieren" im Shopify-Admin
2. Shopify leitet weiter: https://ihre-app.com/auth/callback?code=EXCHANGE_TOKEN
3. App validiert HMAC, tauscht Code gegen Zugriffstoken (privat, nur serverseitig)
4. Shopify Admin API gewährt Bereiche; Token in Sitzung/Datenbank gespeichert
5. Token wird bei der nächsten Anfrage automatisch erneuert, falls abgelaufen

Remix-Implementierung (Token Exchange):

// shopify.app.ts (App-Konfiguration)
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-Admin-Dashboard
  sessionStorage: new PrismaSessionStorage(prisma),
  restResources, // Beinhaltet REST-API-Hilfsfunktionen
});

export default shopify;

Umgebungsvariablen (.env):

SHOPIFY_API_KEY=Ihr-öffentlicher-API-Schlüssel-aus-dem-Partner-Dashboard
SHOPIFY_API_SECRET=Ihr-privater-API-Secret
SHOPIFY_APP_URL=https://ihre-domain.ngrok.io  # oder Produktions-URL
SCOPES=write_products,read_products,write_orders,read_orders

Auth-Routen (routes/auth.$.tsx - Catch-all-Route):

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 findet hier statt
};

Callback-Behandlung (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 enthält:
  // - session.accessToken (gültig für API-Aufrufe)
  // - session.shop (Shop-Domain des Händlers)
  // - session.scope (gewährte Bereiche)
  // - session.state (optionale benutzerdefinierte Daten)

  return redirect('/app'); // Nach Authentifizierung zum Dashboard umleiten
};

Token in API-Aufrufen verwenden (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);

  // Methode 1: Shopifys admin-Helfer verwenden (empfohlen)
  const response = await admin.graphql(`
    query GetProducts {
      products(first: 10) {
        edges {
          node {
            id
            title
            handle
          }
        }
      }
    }
  `);

  // Methode 2: Manueller GraphQL-Aufruf
  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-Aktualisierung (Automatisch): Token-Exchange-Tokens aktualisieren sich automatisch über die Shopify-Sitzungs-Middleware. Keine manuelle Aktualisierung erforderlich:

// Tokens werden bei jeder Anfrage transparent aktualisiert
const response = await admin.graphql(query); // Handhabt Aktualisierung intern

2. Managed Installation (Headless/Benutzerdefinierte Apps)

Anwendungsfall: Headless-Storefront, mobile Apps, Integrationen von Drittanbietern Ablauf: Händler autorisiert App → Shopify generiert dauerhaftes Zugriffstoken (keine Secret-Rotation) Token-Lebensdauer: Langlebig; keine Aktualisierung erforderlich Sicherheit: Token ist dauerhaft; sicher in Umgebungsvariable speichern

Managed Installation Einrichtung:

Im Shopify-Partner-Dashboard:

  1. App-Einstellungen > API-Anmeldeinformationen
  2. "Managed installation" unter Admin-API-Zugriffsbereiche auswählen
  3. Händler erteilt einmalig Berechtigung
  4. Zugriffstoken in Ihre Umgebung kopieren
# .env
SHOPIFY_ADMIN_ACCESS_TOKEN=<SHOPIFY_ADMIN_ACCESS_TOKEN>
SHOPIFY_SHOP_URL=example-shop.myshopify.com

Managed Installation Token verwenden:

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 || '',
    },
  }
);

// Token läuft nie ab; API jederzeit aufrufbar
const query = `query { products(first: 10) { edges { node { id title } } } }`;
const products = await client.request(query);

3. OAuth 2.0 Authorization Code Grant (Legacy, weiterhin unterstützt)

Anwendungsfall: Öffentliche Apps mit traditionellem OAuth-Ablauf Ablauf: Händler klickt auf "Installieren" → App leitet zu Shopify OAuth weiter → Händler autorisiert → App erhält Code → App tauscht Code gegen Token Token-Lebensdauer: Langlebiges Zugriffstoken (kein Ablauf, es sei denn, es wird widerrufen) Sicherheit: Client-Secret erforderlich; PKCE optional

OAuth-Ablaufdiagramm:

1. Händler besucht: https://ihre-app.com/auth
2. App leitet weiter zu: https://ihr-shop.myshopify.com/admin/oauth/authorize?client_id=KEY&scope=write_products&redirect_uri=https://ihre-app.com/auth/callback&state=ZUFALL
3. Händler autorisiert App im Shopify-Admin
4. Shopify leitet zurück: https://ihre-app.com/auth/callback?code=AUTORISIERUNGSCODE&hmac=SIGNATUR&state=ZUFALL&shop=ihr-shop.myshopify.com
5. App validiert HMAC und state
6. App tauscht Code gegen Token (serverseitig, mit Client-Secret)
7. App speichert Token in Datenbank

Express.js OAuth-Beispiel:

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://ihre-app.com/auth/callback';
const SCOPES = 'write_products,read_products';

// Schritt 1: Händler zum Shopify-OAuth weiterleiten
app.get('/auth', (req, res) => {
  const shop = req.query.shop as string;

  if (!shop || !shop.includes('.myshopify.com')) {
    return res.status(400).send('Fehlender oder ungültiger shop-Parameter');
  }

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

  // State in Sitzung (oder Datenbank) für Validierung speichern
  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());
});

// Schritt 2: OAuth-Callback verarbeiten
app.get('/auth/callback', async (req, res) => {
  const { code, hmac, shop, state } = req.query;

  // HMAC validieren
  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('Nicht autorisierte Anfrage erkannt');
  }

  // State validieren
  if (state !== req.session.state) {
    return res.status(401).send('State-Stimmen nicht überein');
  }

  try {
    // Code gegen Zugriffstoken eintauschen
    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;

    // Zugriffstoken speichern (in Datenbank, nicht Sitzung, für Persistenz)
    await storeAccessToken(shop as string, access_token, scope);

    // Zum App-Dashboard umleiten
    res.redirect(`/app?shop=${shop}`);
  } catch (error) {
    console.error('Token-Austauschfehler:', error);
    res.status(500).send('Authentifizierung fehlgeschlagen');
  }
});

// Hilfsfunktion zum Speichern des Tokens
async function storeAccessToken(shop: string, token: string, scope: string) {
  // In Datenbank speichern (Beispiel mit Mock-Speicher)
  const db = {
    shops: {} as Record<string, { token: string; scope: string }>,
  };
  db.shops[shop] = { token, scope };
}

app.listen(3000);

Sitzungsspeicher-Adapter

Zugriffstoken müssen dauerhaft gespeichert werden. Shopify stellt Adapter für gängige Speicher-Backends bereit:

Prisma (Empfohlen für 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])
}

Einrichtung (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,
});

In-Memory (Nur für Entwicklung)

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

const sessionStorage = new MemorySessionStorage();

const shopify = shopifyApp({
  // ...
  sessionStorage, // ACHTUNG: Sitzungen gehen bei App-Neustart verloren; nur für Entwicklung
});

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,
});

Online- vs. Offline-Tokens

Online-Token:

  • Bereich: Berechtigungen des aktuellen Benutzers (normalerweise Admin-Benutzer)
  • Ablauf: 24 Stunden
  • Anwendungsfall: Browserbasierte Aktionen (Polaris-Admin-Dashboard)
  • Einschränkungen: Kann keine Hintergrundjobs ausführen; eingeschränkt, wenn Benutzer abgemeldet ist

Offline-Token:

  • Bereich: Gewährte Bereiche der App
  • Ablauf: Keiner; dauerhaft bis Widerruf
  • Anwendungsfall: Hintergrundjobs, Webhooks, geplante Aufgaben
  • Einschränkungen: Keine; standardmäßig für App-Operationen verwenden

Offline-Token im Token Exchange anfordern:

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

// Remix fordert standardmäßig automatisch ein Offline-Token an
// Keine Aktion erforderlich; session.accessToken für API-Aufrufe verwenden

Offline-Token für Webhooks verwenden:

// 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 ist Offline-Token; hier gültig
    console.log(`Webhook verarbeitet mit Token für Shop: ${session.shop}`);
  },
  webhooksConfig
);

App-Proxy-Authentifizierung

Anwendungsfall: Storefront (öffentlich zugängliche) Anfragen an das App-Backend Authentifizierung: HMAC-Signaturvalidierung (wie bei Webhooks) Ablauf: Storefront → Liquid-Proxy-Anfrage → App-Backend (validiert HMAC) → Antwort

App-Proxy einrichten (shopify.app.toml):

[[extensions]]
type = "app_proxy"
name = "Storefront API"
url = "/api/proxy"
subpath = "loyalty"  # Anfragen an /apps/loyalty/* werden hierher geleitet

App-Proxy-Handler (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') || '';

  // Erstelle die zu validierende Nachricht (alle Parameter außer HMAC)
  const params = new URLSearchParams(url.searchParams);
  params.delete('hmac');
  const message = params.toString();

  // HMAC validieren
  const expectedHmac = crypto
    .createHmac('sha256', process.env.SHOPIFY_API_SECRET || '')
    .update(message, 'utf8')
    .digest('hex');

  if (hmac !== expectedHmac) {
    return json({ error: 'Ungültige Anfrage' }, { status: 401 });
  }

  // Anfrage ist authentifiziert; verarbeite sie
  return json({ message: 'Proxy-Anfrage erfolgreich' });
};

Fehlerbehebung

ProblemUrsacheLösung
403 ForbiddenUngültige/abgelaufene ZugriffstokenToken mit Shopify-Sitzungs-Middleware aktualisieren
Invalid HMACFalsch berechneter HMACÜberprüfen Sie die Parameterreihenfolge (alphabetisch), Secret und Kodierung
OAuth redirect_uri mismatchNicht übereinstimmende Weiterleitungs-URLStimmt die URL in der Partner-Dashboard-Konfiguration genau überein?
Session not foundSitzungsspeicher leer oder nicht konfiguriertPrisma migrieren (npx prisma db push) oder Redis-Verbindung überprüfen
Token exchange failedUngültiger Exchange-CodeCode nur einmalig und innerhalb von 10 Minuten verwenden

Best Practices

  1. Token-Exchange für 2024+ Apps verwenden – Vermeiden Sie direkte OAuth-Implementierungen, es sei denn, Sie benötigen Legacy-Kompatibilität.
  2. Offline-Tokens standardmäßig – Verwenden Sie Offline-Tokens für Hintergrundvorgänge; Online-Tokens sind auf 24 Stunden und Benutzersitzungen beschränkt.
  3. Sitzungen sicher speichern – Verwenden Sie Prisma, Redis oder DynamoDB für die Produktion; niemals In-Memory für Produktion.
  4. HMAC immer validieren – Überprüfen Sie HMAC bei Callbacks und Webhooks, um CSRF- und Replay-Angriffe zu verhindern.
  5. Bereiche minimieren – Fordern Sie nur die Bereiche an, die Ihre App benötigt; minimieren Sie so die Exposition.
  6. Tokens rotieren – Token-Exchange rotiert automatisch; bei Legacy-OAuth implementieren Sie regelmäßige Token-Rotation.

Verwandte Skills