PWA Kit v2 向けハイブリッド認証の実装

このガイドでは、PWA Kit アプリケーションをバージョン 2.10.0 にアップグレードし、ハイブリッド認証を実装する方法について説明します。PWA Kit 2.10.0 リリースでは、ハイブリッド認証との互換性や @salesforce/commerce-sdk-react パッケージとの統合など、重要な変更が導入されています。PWA Kit v2.10.0 は、SLAS パブリッククライアントのみをサポートします。詳細については、pwa-kit GitHub diff 全体を確認して、コード変更について理解を深めてください。

ハイブリッド認証の導入をこれから開始する新規のお客様は、PWA Kit v3.10.0 以降を使用して開始する必要があります。PWA Kit v3 でハイブリッド認証を実装する方法については、ハイブリッド実装ガイドを参照してください。

Important

前提条件 

アップグレードを開始する前に、以下を満たしていることを確認してください。

  • Node.js バージョン 18.x または 20.x (14.x/16.x のサポートからアップグレード)
  • NPM バージョン 9.x または 10.x (6.x/7.x/8.x のサポートからアップグレード)
  • Business Manager インスタンスでハイブリッド認証を有効にし、Managed Runtime で Cookie を有効にしていること。詳細については、ハイブリッド認証を参照してください。

認証システムの更新 

最も重要な更新は、新しい Commerce SDK React 認証システムの導入です。

新しい依存関係 

1{
2  "@salesforce/commerce-sdk-react": "^3.4.0",
3  "@tanstack/react-query": "4.28.0"
4}

段階的なアップグレード手順 

手順 1: 環境の準備 

  1. 現在の Node.js と npm のバージョンを確認します。

    1node --version
    2npm --version

    Node.js バージョンが 18.x 未満の場合は、先にアップグレードしてください。npm のバージョンが 9.x 未満の場合も、先にアップグレードしてください。

    アップグレード前 (PWA Kit v2.9.x): 

    1{
    2  "engines": {
    3    "node": "^14.0.0 || ^16.0.0 || ^18.0.0 || ^20.0.0",
    4    "npm": "^6.14.4 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
    5  }
    6}

    アップグレード後 (PWA Kit v2.10.0): 

    1{
    2  "engines": {
    3    "node": "^18.0.0 || ^20.0.0",
    4    "npm": "^9.0.0 || ^10.0.0"
    5  }
    6}

手順 2: 依存関係の更新 

  1. package.json ファイルを開き、次のセクションを更新します。

    "engines" セクションを更新します。

    1{
    2  "engines": {
    3    "node": "^18.0.0 || ^20.0.0",
    4    "npm": "^9.0.0 || ^10.0.0"
    5  }
    6}

    "devDependencies" セクションを更新して、次の依存関係を追加またはバージョン更新します。

    1{
    2  "devDependencies": {
    3    "@salesforce/commerce-sdk-react": "^3.4.0",
    4    "@tanstack/react-query": "4.28.0",
    5    "commerce-sdk-isomorphic": "^3.3.0",
    6    "jwt-decode": "^4.0.0",
    7    "pwa-kit-dev": "2.10.0",
    8    "pwa-kit-react-sdk": "2.10.0",
    9    "pwa-kit-runtime": "2.10.0",
    10    "@testing-library/react": "^12.1.5",
    11    "@testing-library/react-hooks": "^8.0.1",
    12    "@testing-library/user-event": "^14.4.3",
    13    "ajv": "^8.17.1",
    14    "ajv-keywords": "^5.1.0"
    15  }
    16}
  2. package.json ファイルを保存します。

  3. 新しい依存関係をクリーンインストールします。

    1rm -rf node_modules package-lock.json
    2npm install

@testing-library/* の依存関係は任意です。これらが必要になるのは、react-testing-library を使用して単体テストを実装している場合のみです。

Note

手順 3: 構成の更新 

  1. config/default.js ファイルを開きます。

  2. ssrFunctionNodeVersion を含む行を見つけて更新します。

    1// Change this line:
    2ssrFunctionNodeVersion: '18.x', // Or 20.x if you upgraded to Node 20.
  3. config/default.js ファイルを保存します。

手順 4: 認証の移行 

新しい Commerce SDK React システムを使用するように、認証実装を更新します。

  1. 古い認証ファイルを削除します。

    1rm app/commerce-api/auth.js
    2rm app/commerce-api/pkce.js
  2. utils.js ファイルを更新します。

    1# Open app/commerce-api/utils.js and remove authentication-related functions

    ファイルの先頭から、次の import を削除します。

    1// REMOVE these lines from app/commerce-api/utils.js
    2import jwtDecode from "jwt-decode";
    3import { refreshTokenGuestStorageKey, refreshTokenRegisteredStorageKey } from "./constants";

    app/commerce-api/utils.js から、次の関数を削除します。

    1// REMOVE these functions from app/commerce-api/utils.js
    2export function isTokenExpired(token) { ... }
    3export function createGetTokenBody(urlString, slasCallbackEndpoint, codeVerifier) { ... }
    4export function hasSFRAAuthStateChanged(storage, storageCopy) { ... }

    keysToCamelcreateOcapiFetch など、その他のユーティリティ関数は引き続き必要なため、そのまま残します。

  3. useCustomer フックを更新します。app/hooks/use-customer.js ファイルを開き、認証メソッドを更新します。

    1// Add these new imports at the top of the file:
    2import { AuthHelpers } from '@salesforce/commerce-sdk-react'
    3import { useAuthHelper } from '@salesforce/commerce-sdk-react'
    4
    5// Find the login and logout methods in useCustomer hook and update them:
    6
    7// Old implementation (v2.9.x)
    8const login = async (credentials) => {
    9  const auth = new Auth();
    10  await auth.login(credentials);
    11  // ... rest of login logic
    12};
    13
    14const logout = async () => {
    15  const auth = new Auth();
    16  await auth.logout();
    17  // ... rest of logout logic
    18};
    19
    20// New implementation (v2.10.0)
    21export default function useCustomer() {
    22 const api = useCommerceAPI()
    23 const {customer, setCustomer} = useContext(CustomerContext)
    24
    25 const login = useAuthHelper(AuthHelpers.LoginRegisteredUserB2C)
    26 const logout = useAuthHelper(AuthHelpers.Logout)
    27
    28   const getSkeletonCustomer = () => {
    29       return {
    30           customerId: api.auth.get('customer_id'),
    31           authType: api.auth.get('customer_type')
    32       }
    33   }
    34
    35 const self = useMemo(() => {
    36   return {
    37     ... customer,
    38
    39     // Other useCustomer hook functions
    40     ...
    41     // The login method is now handled by the useAuthHelper hook
    42     async login(credentials) {
    43         await api.auth.ready()
    44         let skeletonCustomer = getSkeletonCustomer()
    45         if (credentials) {
    46             await login.mutateAsync({
    47                 username: credentials.email,
    48                 password: credentials.password
    49             })
    50             skeletonCustomer = getSkeletonCustomer()
    51         }
    52       // ... rest of login logic
    53       },
    54
    55       async logout() {
    56         await logout.mutateAsync()
    57         await api.auth.ready()
    58         const skeletonCustomer = getSkeletonCustomer()
    59         setCustomer(skeletonCustomer)
    60         // ... rest of logout logic
    61       }
    62   }
    63 })

useCustomer フックは、新しい Commerce SDK React の認証メソッドを使用するようになりました。既存のログインコンポーネントと登録コンポーネントは、これまでと同じ login() 関数と register() 関数を呼び出すため、変更する必要はありません。

Note

手順 5: Commerce API 実装の更新 

Commerce API の実装は、@salesforce/commerce-sdk-react パッケージの新しい transformSDKClient ユーティリティを使用するように大幅に更新されています。詳細については、パッケージの README.md を参照してください。

Note

  1. app/commerce-api/index.js ファイルを開きます。

  2. ファイルの先頭にある import を更新します。

    1// Remove these old imports:
    2import Auth from "./auth";
    3import { isError } from "./utils";
    4
    5// Add these new imports:
    6import Auth from "@salesforce/commerce-sdk-react/auth";
    7import { transformSDKClient } from "@salesforce/commerce-sdk-react/utils";
    8import { DWSID_HEADER_KEY } from "./constants";
  3. auth モジュールのインスタンス化を更新します。

    1// Old implementation (v2.9.x)
    2
    3this._config = { proxy, ...restConfig };
    4
    5this.auth = new Auth(this); // Update this with new authConfig

    次の新しいコードに置き換えます。

    1// Old implementation (v2.9.x)
    2
    3this._config = { proxy, ...restConfig };
    4
    5// Add new authConfig object
    6this._authConfig = {
    7  redirectURI: `${getAppOrigin()}/callback`,
    8  proxy,
    9  locale: this._config.locale,
    10  currency: this._config.currency,
    11  ...this._config.parameters,
    12  ...this._config.headers,
    13};
    14
    15this.auth = new Auth(this._authConfig); // pass authConfig as param to Auth class constructor.
  4. SDK クライアントがインスタンス化されているセクション (100 行目から 150 行目付近) を見つけて、SDK インスタンス化ロジック全体を置き換えます。 次の古いコードを削除します。

    1// Old implementation (v2.9.x) - using Proxy
    2const SdkClass = apiConfigs[key].api;
    3self._sdkInstances = {
    4  ...self._sdkInstances,
    5  [key]: new Proxy(new SdkClass(this._config), {
    6    get: function (obj, prop) {
    7      // ... proxy implementation
    8    },
    9  }),
    10};

    次の新しいコードに置き換えます。

    1// New implementation (v2.10.0) - using transformSDKClient
    2const SdkClass = apiConfigs[key].api;
    3const sdkClient = new SdkClass(this._config);
    4self._sdkInstances = {
    5  ...self._sdkInstances,
    6  [key]: transformSDKClient(sdkClient, {
    7    props: this._config,
    8    transformer: async (_, methodName, options) => {
    9      const { fetchOptions = {} } = options;
    10      if (fetchOptions.ignoreHooks) {
    11        return options;
    12      }
    13
    14      const { locale, currency } = this._config;
    15
    16      // Inject the locale and currency to the API call via its parameters.
    17      const { sendLocale = true, sendCurrency = false } = apiConfigs[key];
    18
    19      const includeGlobalLocale = Array.isArray(sendLocale)
    20        ? sendLocale.includes(methodName)
    21        : !!sendLocale;
    22
    23      const includeGlobalCurrency = Array.isArray(sendCurrency)
    24        ? sendCurrency.includes(methodName)
    25        : !!sendCurrency;
    26
    27      fetchOptions["parameters"] = {
    28        ...(includeGlobalLocale ? { locale } : {}),
    29        ...(includeGlobalCurrency ? { currency } : {}),
    30        ...fetchOptions?.parameters,
    31      };
    32
    33      // Handle auth logic (replacing willSendRequest functionality)
    34      let dwsidHeader = {};
    35      const dwsid = self.auth.get("dwsid");
    36      if (dwsid) {
    37        dwsidHeader = {
    38          [DWSID_HEADER_KEY]: dwsid,
    39        };
    40      }
    41
    42      // Special handling for auth methods
    43      if (
    44        methodName === "authenticateCustomer" ||
    45        methodName === "authorizeCustomer" ||
    46        methodName === "getAccessToken"
    47      ) {
    48        return {
    49          ...options.parameters,
    50          headers: {
    51            ...options.headers,
    52            ...fetchOptions.headers,
    53          },
    54          credentials: "same-origin", // Required for SLAS calls to set dwsid cookie
    55          ...fetchOptions,
    56        };
    57      }
    58
    59      const { access_token: token } = await self.auth.ready();
    60      return {
    61        ...options,
    62        headers: {
    63          ...options.headers,
    64          ...dwsidHeader,
    65          Authorization: `Bearer ${token}`,
    66        },
    67        // Add cache breaker for Storefront Preview
    68        parameters: {
    69          ...options.parameters,
    70          ...(this.isStorefrontPreview ? { c_cache_breaker: Date.now() } : {}),
    71        },
    72      };
    73    },
    74  }),
    75};
  5. CommerceAPI クラスから、次のメソッドを見つけて削除します。

    1// Remove these methods completely:
    2async willSendRequest(methodName, ...params) {
    3    // ... entire method
    4}
    5
    6didReceiveResponse(response, args) {
    7    // ... entire method
    8}
  6. 定数ファイルを更新します。 app/commerce-api/constants.js に新しい定数を追加します。

    1export const DWSID_HEADER_KEY = "sfdc_dwsid";

手順 6: Commerce API コンテキストの更新 

Commerce API コンテキストは、Commerce SDK React および React Query と統合するように更新されています。

Note

  1. app/commerce-api/contexts.js ファイルを開きます。

  2. ファイルの先頭に、次の import を追加します。

    1import { CommerceApiProvider as CommerceSDKReactProvider } from "@salesforce/commerce-sdk-react";
    2import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
    3import { getAppOrigin } from "pwa-kit-react-sdk/utils/url";
    4import { isServer } from "../../pwa-kit-managed-runtime/utils/utils";
  3. import の後に、QueryClient の構成を追加します。

    1const queryClientOptions = {
    2  queryClientConfig: {
    3    defaultOptions: {
    4      queries: {
    5        retry: false,
    6        refetchOnWindowFocus: false,
    7        staleTime: 10 * 1000,
    8        ...(isServer ? { retryOnMount: false } : {}),
    9      },
    10      mutations: {
    11        retry: false,
    12      },
    13    },
    14  },
    15  beforeHydrate: (data) => {
    16    const now = Date.now();
    17    const updateQueryTimeStamp = ({ state }) => {
    18      state.dataUpdatedAt = now;
    19    };
    20    data?.mutations?.forEach(updateQueryTimeStamp);
    21    data?.queries?.forEach(updateQueryTimeStamp);
    22    return data;
    23  },
    24};
    25
    26const queryClient = new QueryClient(queryClientOptions);
  4. 既存の CommerceAPIProvider を、次の新しい実装に置き換えます。

    1export const CommerceAPIProvider = ({ value, children }) => {
    2  const { api, site, locale } = value;
    3  const apiClients = api._sdkInstances;
    4
    5  const { shortCode, clientId, organizationId } = api.getConfig().parameters;
    6  const { proxy } = api.getConfig();
    7
    8  return (
    9    <CommerceAPIContext.Provider value={api}>
    10      <QueryClientProvider client={queryClient}>
    11        <CommerceSDKReactProvider
    12          shortCode={shortCode}
    13          clientId={clientId}
    14          organizationId={organizationId}
    15          siteId={site?.id}
    16          locale={locale?.id}
    17          currency={locale?.preferredCurrency}
    18          redirectURI={`${getAppOrigin()}/callback`}
    19          proxy={proxy}
    20          apiClients={apiClients}
    21          disableAuthInit={true}
    22        >
    23          {children}
    24        </CommerceSDKReactProvider>
    25      </QueryClientProvider>
    26    </CommerceAPIContext.Provider>
    27  );
    28};
  5. app/commerce-api/contexts.js ファイルを保存します。

手順 7: アプリケーション構成プロバイダーの更新 

**重要な変更: ** CommerceAPIProvider で必要な props 構造が変更されました。

Important

  1. app/components/_app-config/index.jsx ファイルを開きます。

  2. CommerceAPIProvider コンポーネントを見つけて更新します。

    次のコードを:

    1// Old implementation (v2.9.x)
    2<CommerceAPIProvider value={locals.api}>

    次のコードに置き換えます。

    1// New implementation (v2.10.0)
    2<CommerceAPIProvider value={locals}>
  3. app/components/_app-config/index.jsx ファイルを保存します。

手順 8: 認証初期化のクリーンアップ 

  1. app/components/_app/index.jsx ファイルを開きます。

  2. login 呼び出しを削除します。

    1// Auth initialization is now handled by calling await self.auth.ready() in commerce-api/index.js
    2// Remove this line.
    3await api.auth.login();

手順 9: アップグレードのテスト 

  1. 開発サーバーを起動します。

    1npm start
  2. ビルドを実行して、エラーがないことを確認します。

    1npm run build

(オプション) Commerce SDK React のクエリフックの使用 

PWA Kit v2.10.0 では、@salesforce/commerce-sdk-react の強力なクエリフックを使用できるようになりました。これらのフックは、ハイブリッドおよび非ハイブリッドの両方の PWA Kit v2.x ストアフロントで、自動キャッシュ、読み込み状態、エラー処理を提供します。ページでの使用方法は次のとおりです。

例: シンプルな注文リストコンポーネント 

新しいクエリフックを使用して、最小限の注文リストコンポーネントを作成します。

1import React from "react";
2import { useCustomerOrders } from "@salesforce/commerce-sdk-react";
3import useCustomer from "../../commerce-api/hooks/useCustomer";
4
5const SimpleOrderList = () => {
6  const customer = useCustomer();
7
8  const { data: { data: orders } = {}, isLoading } = useCustomerOrders(
9    { parameters: { customerId: customer?.customerId } },
10    { enabled: !!customer?.customerId },
11  );
12
13  if (isLoading) return <div>Loading orders...</div>;
14
15  return (
16    <div>
17      <h2>My Orders</h2>
18      {orders?.map((order) => (
19        <div key={order.orderNo}>
20          <p>
21            Order #{order.orderNo} - {order.status}
22          </p>
23          <p>Total: ${order.orderTotal}</p>
24        </div>
25      ))}
26    </div>
27  );
28};
29
30export default SimpleOrderList;

主なメリット 

新しいクエリフックの主なメリットは次のとおりです。

  • 自動キャッシュ - データがキャッシュされ、共有されます。
  • 読み込み状態 - 読み込み状態の管理が組み込まれています。
  • エラー処理 - エラー状態が自動的に処理されます。
  • 最適化されたフェッチ - 顧客情報を利用できる場合にのみフェッチされます。
  • コードの簡素化 - 状態管理を手動で行う必要が減ります。

追加リソース