# Implement Hybrid Authentication for PWA Kit v2

:::important
**Who should use this guide:** Only **existing PWA Kit v2.x projects** that want to add Hybrid Auth by upgrading to v2.10.0. PWA Kit v2.10.0 supports **SLAS Public Clients only**.

**Starting a new Hybrid Auth implementation?** Don't use this guide. New implementations must start on **PWA Kit v3**—see [Configure a Hybrid Storefront with Hybrid Auth (PWA Kit)](/docs/commerce/b2c-commerce/guide/hybrid-auth.md) for the supported version and prerequisites.
:::

Use this guide to upgrade your existing PWA Kit application to version 2.10.0 and implement Hybrid Auth. The PWA Kit 2.10.0 release introduces significant changes, such as compatibility with Hybrid Auth and integration with the `@salesforce/commerce-sdk-react` package. For additional details, check out the [complete pwa-kit GitHub diff](https://github.com/SalesforceCommerceCloud/pwa-kit/compare/v2.9.1...v2.10.0) to get a better understanding of the code changes.

## Prerequisites

Before starting the upgrade process, make sure that you have:

- Node.js version 18.x or 20.x (upgraded from 14.x/16.x support)
- NPM version 9.x or 10.x (upgraded from 6.x/7.x/8.x support)
- Enable Hybrid Auth on your Business Manager instance. For details, see [Shared Hybrid Auth Setup](/docs/commerce/b2c-commerce/guide/hybrid-auth-shared-setup.md). Then enable cookies on Managed Runtime, as described in [Enable Cookies on Managed Runtime](/docs/commerce/b2c-commerce/guide/hybrid-auth.md#step-5-enable-cookies-on-managed-runtime).

## Authentication System Updates

The most significant update is the introduction of the new Commerce SDK React authentication system.

### New Dependencies

```json
{
  "@salesforce/commerce-sdk-react": "^4.0.0",
  "@tanstack/react-query": "4.28.0"
}
```

## Step-by-Step Upgrade Process

### Step 1: Prepare Your Environment

1. Check your current Node.js and npm versions.

   ```bash
   node --version
   npm --version
   ```

   If your Node.js version is below 18.x, upgrade it first. If your npm version is below 9.x, upgrade it first.

   #### Before the upgrade (PWA Kit v2.9.x):

   ```json
   {
     "engines": {
       "node": "^14.0.0 || ^16.0.0 || ^18.0.0 || ^20.0.0",
       "npm": "^6.14.4 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
     }
   }
   ```

   #### After the upgrade (PWA Kit v2.10.0):

   ```json
   {
     "engines": {
       "node": "^18.0.0 || ^20.0.0",
       "npm": "^9.0.0 || ^10.0.0"
     }
   }
   ```

### Step 2: Update Dependencies

1. Open your `package.json` file and update the following sections.

   Update the `"engines"` section.

   ```json
   {
     "engines": {
       "node": "^18.0.0 || ^20.0.0",
       "npm": "^9.0.0 || ^10.0.0"
     }
   }
   ```

   Update the `"devDependencies"` section with these new dependencies and version bumps.

   ```json
   {
     "devDependencies": {
       "@salesforce/commerce-sdk-react": "^3.4.0",
       "@tanstack/react-query": "4.28.0",
       "commerce-sdk-isomorphic": "^3.3.0",
       "jwt-decode": "^4.0.0",
       "pwa-kit-dev": "2.10.0",
       "pwa-kit-react-sdk": "2.10.0",
       "pwa-kit-runtime": "2.10.0",
       "@testing-library/react": "^12.1.5",
       "@testing-library/react-hooks": "^8.0.1",
       "@testing-library/user-event": "^14.4.3",
       "ajv": "^8.17.1",
       "ajv-keywords": "^5.1.0"
     }
   }
   ```

2. Save the `package.json` file.

3. Clean install the new dependencies.

   ```bash
   rm -rf node_modules package-lock.json
   npm install
   ```

:::note
The `@testing-library/*` dependencies are optional and required only if you've implemented unit tests using `react-testing-library`.
:::

### Step 3: Update the Configuration

1. Open your `config/default.js` file.

2. Find the line that contains `ssrFunctionNodeVersion` and update it.

   ```javascript
   // Change this line:
   ssrFunctionNodeVersion: '18.x', // Or 20.x if you upgraded to Node 20.
   ```

3. Save the `config/default.js` file.

### Step 4: Authentication Migration

Update your authentication implementation with the new Commerce SDK React system.

1. Remove old authentication files.

   ```bash
   rm app/commerce-api/auth.js
   rm app/commerce-api/pkce.js
   ```

2. Update the `utils.js` file.

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

   Remove these imports from the top of the file.

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

   Remove these functions from `app/commerce-api/utils.js`.

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

   Keep all other utility functions, including `keysToCamel` and `createOcapiFetch`, as they're still needed.

3. Update the `useCustomer` hook. Open your `app/hooks/use-customer.js` file and update the authentication methods.

   ```javascript
   // Add these new imports at the top of the file:
   import { AuthHelpers } from '@salesforce/commerce-sdk-react'
   import { useAuthHelper } from '@salesforce/commerce-sdk-react'

   // Find the login and logout methods in useCustomer hook and update them:

   // Old implementation (v2.9.x)
   const login = async (credentials) => {
     const auth = new Auth();
     await auth.login(credentials);
     // ... rest of login logic
   };

   const logout = async () => {
     const auth = new Auth();
     await auth.logout();
     // ... rest of logout logic
   };

   // New implementation (v2.10.0)
   export default function useCustomer() {
    const api = useCommerceAPI()
    const {customer, setCustomer} = useContext(CustomerContext)

    const login = useAuthHelper(AuthHelpers.LoginRegisteredUserB2C)
    const logout = useAuthHelper(AuthHelpers.Logout)

      const getSkeletonCustomer = () => {
          return {
              customerId: api.auth.get('customer_id'),
              authType: api.auth.get('customer_type')
          }
      }

    const self = useMemo(() => {
      return {
        ... customer,

        // Other useCustomer hook functions
        ...
        // The login method is now handled by the useAuthHelper hook
        async login(credentials) {
            await api.auth.ready()
            let skeletonCustomer = getSkeletonCustomer()
            if (credentials) {
                await login.mutateAsync({
                    username: credentials.email,
                    password: credentials.password
                })
                skeletonCustomer = getSkeletonCustomer()
            }
          // ... rest of login logic
          },

          async logout() {
            await logout.mutateAsync()
            await api.auth.ready()
            const skeletonCustomer = getSkeletonCustomer()
            setCustomer(skeletonCustomer)
            // ... rest of logout logic
          }
      }
    })
   ```

:::note
The `useCustomer` hook now uses the new Commerce SDK React authentication methods. Your existing login and registration components don't need any changes since they call the same `login()` and `register()` functions.
:::

### Step 5: Update the Commerce API Implementation

:::note
The Commerce API implementation has been significantly updated to use the new `transformSDKClient` utility from `@salesforce/commerce-sdk-react` package. See the [README.md](https://github.com/SalesforceCommerceCloud/pwa-kit/blob/develop/packages/commerce-sdk-react/README.md#advanced-customizing-sdk-clients-with-transformsdkclient) for the package to learn more.
:::

1. Open your `app/commerce-api/index.js` file.

2. Update the imports at the top of the file.

   ```javascript
   // Remove these old imports:
   import Auth from "./auth";
   import { isError } from "./utils";

   // Add these new imports:
   import Auth from "@salesforce/commerce-sdk-react/auth";
   import { transformSDKClient } from "@salesforce/commerce-sdk-react/utils";
   import { DWSID_HEADER_KEY } from "./constants";
   ```

3. Update the auth module instantiation.

   ```javascript
   // Old implementation (v2.9.x)

   this._config = { proxy, ...restConfig };

   this.auth = new Auth(this); // Update this with new authConfig
   ```

   Replace with this new code:

   ```javascript
   // Old implementation (v2.9.x)

   this._config = { proxy, ...restConfig };

   // Add new authConfig object
   this._authConfig = {
     redirectURI: `${getAppOrigin()}/callback`,
     proxy,
     locale: this._config.locale,
     currency: this._config.currency,
     ...this._config.parameters,
     ...this._config.headers,
   };

   this.auth = new Auth(this._authConfig); // pass authConfig as param to Auth class constructor.
   ```

4. Find the section where SDK clients are instantiated (around line 100-150) and replace the entire SDK instantiation logic.
   Remove this old code:

   ```javascript
   // Old implementation (v2.9.x) - using Proxy
   const SdkClass = apiConfigs[key].api;
   self._sdkInstances = {
     ...self._sdkInstances,
     [key]: new Proxy(new SdkClass(this._config), {
       get: function (obj, prop) {
         // ... proxy implementation
       },
     }),
   };
   ```

   Replace with this new code:

   ```javascript
   // New implementation (v2.10.0) - using transformSDKClient
   const SdkClass = apiConfigs[key].api;
   const sdkClient = new SdkClass(this._config);
   self._sdkInstances = {
     ...self._sdkInstances,
     [key]: transformSDKClient(sdkClient, {
       props: this._config,
       transformer: async (_, methodName, options) => {
         const { fetchOptions = {} } = options;
         if (fetchOptions.ignoreHooks) {
           return options;
         }

         const { locale, currency } = this._config;

         // Inject the locale and currency to the API call via its parameters.
         const { sendLocale = true, sendCurrency = false } = apiConfigs[key];

         const includeGlobalLocale = Array.isArray(sendLocale)
           ? sendLocale.includes(methodName)
           : !!sendLocale;

         const includeGlobalCurrency = Array.isArray(sendCurrency)
           ? sendCurrency.includes(methodName)
           : !!sendCurrency;

         fetchOptions["parameters"] = {
           ...(includeGlobalLocale ? { locale } : {}),
           ...(includeGlobalCurrency ? { currency } : {}),
           ...fetchOptions?.parameters,
         };

         // Handle auth logic (replacing willSendRequest functionality)
         let dwsidHeader = {};
         const dwsid = self.auth.get("dwsid");
         if (dwsid) {
           dwsidHeader = {
             [DWSID_HEADER_KEY]: dwsid,
           };
         }

         // Special handling for auth methods
         if (
           methodName === "authenticateCustomer" ||
           methodName === "authorizeCustomer" ||
           methodName === "getAccessToken"
         ) {
           return {
             ...options.parameters,
             headers: {
               ...options.headers,
               ...fetchOptions.headers,
             },
             credentials: "same-origin", // Required for SLAS calls to set dwsid cookie
             ...fetchOptions,
           };
         }

         const { access_token: token } = await self.auth.ready();
         return {
           ...options,
           headers: {
             ...options.headers,
             ...dwsidHeader,
             Authorization: `Bearer ${token}`,
           },
           // Add cache breaker for Storefront Preview
           parameters: {
             ...options.parameters,
             ...(this.isStorefrontPreview ? { c_cache_breaker: Date.now() } : {}),
           },
         };
       },
     }),
   };
   ```

5. Find and remove these methods from the CommerceAPI class:

   ```javascript
   // Remove these methods completely:
   async willSendRequest(methodName, ...params) {
       // ... entire method
   }

   didReceiveResponse(response, args) {
       // ... entire method
   }
   ```

6. Update the constants file.
   Add a new constant in `app/commerce-api/constants.js`.

   ```javascript
   export const DWSID_HEADER_KEY = "sfdc_dwsid";
   ```

### Step 6: Update Commerce API Contexts

:::note
The Commerce API contexts have been updated to integrate with Commerce SDK React and React Query.
:::

1. Open your `app/commerce-api/contexts.js` file.

2. Add these new imports at the top.

   ```javascript
   import { CommerceApiProvider as CommerceSDKReactProvider } from "@salesforce/commerce-sdk-react";
   import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
   import { getAppOrigin } from "pwa-kit-react-sdk/utils/url";
   import { isServer } from "../../pwa-kit-managed-runtime/utils/utils";
   ```

3. Add the `QueryClient` configuration after the imports.

   ```javascript
   const queryClientOptions = {
     queryClientConfig: {
       defaultOptions: {
         queries: {
           retry: false,
           refetchOnWindowFocus: false,
           staleTime: 10 * 1000,
           ...(isServer ? { retryOnMount: false } : {}),
         },
         mutations: {
           retry: false,
         },
       },
     },
     beforeHydrate: (data) => {
       const now = Date.now();
       const updateQueryTimeStamp = ({ state }) => {
         state.dataUpdatedAt = now;
       };
       data?.mutations?.forEach(updateQueryTimeStamp);
       data?.queries?.forEach(updateQueryTimeStamp);
       return data;
     },
   };

   const queryClient = new QueryClient(queryClientOptions);
   ```

4. Replace the existing `CommerceAPIProvider` with this new implementation.

   ```javascript
   export const CommerceAPIProvider = ({ value, children }) => {
     const { api, site, locale } = value;
     const apiClients = api._sdkInstances;

     const { shortCode, clientId, organizationId } = api.getConfig().parameters;
     const { proxy } = api.getConfig();

     return (
       <CommerceAPIContext.Provider value={api}>
         <QueryClientProvider client={queryClient}>
           <CommerceSDKReactProvider
             shortCode={shortCode}
             clientId={clientId}
             organizationId={organizationId}
             siteId={site?.id}
             locale={locale?.id}
             currency={locale?.preferredCurrency}
             redirectURI={`${getAppOrigin()}/callback`}
             proxy={proxy}
             apiClients={apiClients}
             disableAuthInit={true}
           >
             {children}
           </CommerceSDKReactProvider>
         </QueryClientProvider>
       </CommerceAPIContext.Provider>
     );
   };
   ```

5. Save the `app/commerce-api/contexts.js` file.

### Step 7: Update the App Configuration Provider

:::important
**Critical Change:** The CommerceAPIProvider now requires different `props` structure.
:::

1. Open your `app/components/_app-config/index.jsx` file.

2. Find the `CommerceAPIProvider` component and update it.

   Replace this code:

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

   With this code:

   ```javascript
   // New implementation (v2.10.0)
   <CommerceAPIProvider value={locals}>
   ```

3. Save the `app/components/_app-config/index.jsx` file.

### Step 8: Cleanup Auth Initialization

1. Open your `app/components/_app/index.jsx` file.

2. Remove the login call.

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

### Step 9: Test Your Upgrade

1. Start the development server.

   ```bash
   npm start
   ```

2. Run the build to check for errors.

   ```bash
   npm run build
   ```

## (Optional) Use Commerce SDK React Query Hooks

With PWA Kit v2.10.0, you now have access to powerful query hooks from `@salesforce/commerce-sdk-react` that provide automatic caching, loading states, and error handling for both hybrid and non-hybrid PWA Kit v2.x storefronts. Here's how to use them in your pages.

### Example: Simple Order List Component

Create a minimal order list component using the new query hooks.

```javascript
import React from "react";
import { useCustomerOrders } from "@salesforce/commerce-sdk-react";
import useCustomer from "../../commerce-api/hooks/useCustomer";

const SimpleOrderList = () => {
  const customer = useCustomer();

  const { data: { data: orders } = {}, isLoading } = useCustomerOrders(
    { parameters: { customerId: customer?.customerId } },
    { enabled: !!customer?.customerId },
  );

  if (isLoading) return <div>Loading orders...</div>;

  return (
    <div>
      <h2>My Orders</h2>
      {orders?.map((order) => (
        <div key={order.orderNo}>
          <p>
            Order #{order.orderNo} - {order.status}
          </p>
          <p>Total: ${order.orderTotal}</p>
        </div>
      ))}
    </div>
  );
};

export default SimpleOrderList;
```

## Key Benefits

Check out the key benefits of the new query hooks.

- Automatic caching—Data is cached and shared.
- Loading states—Built-in loading management.
- Error handling—Automatic error states.
- Optimized fetching—Only fetches when customer is available.
- Simplified code—Less manual state management.

## Additional Resources

- [PWA Kit Overview](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/pwa-kit-overview.md)
- [Hybrid Storefront Basket Guidance](/docs/commerce/b2c-commerce/guide/hybrid-storefront-baskets.md#hybrid-storefront-best-practices-for-working-with-baskets)
- [Commerce SDK React Documentation](https://salesforcecommercecloud.github.io/pwa-kit/)
