Authentication & Session
To authenticate customers with the Recharge JavaScript SDK you will need to do it after customers authenticate with Shopify. It is recommended to piggy-back on the Shopify login APIs and use Shopify's session for storing the Recharge session. You can also clear the session on logout.
In the example below we are showing one way of doing this that works specifically with a HydrogenSession
object for saving the Recharge Session
within that session. If you aren't using the default Session storage options for Hydrogen you may need to alter this approach.
Note that both tokens used below are Shopify
tokens:
storefrontToken
- https://shopify.dev/api/admin-rest/2023-01/resources/storefrontaccesstokenshopifyCustomerAccessToken
- https://shopify.dev/api/storefront/2023-01/mutations/customerAccessTokenCreate
Login Utility Function
Create a rechargeUtil.ts
module where you can export a loginRecharge
function that can be reused.
We are also adding rechargeQueryWrapper
to help with data fetching (which is covered in the next section). This helper function handles any needed login retries or/and error handling.
NOTE In this example rechargeQueryWrapper
can update the session object. Make sure you are committing the session (in case it changes) after calling the rechargeQueryWrapper
function in the response from your loader
/action
.
- JavaScript
- TypeScript
/app/lib/rechargeUtils.js
import { loginShopifyApi } from '@rechargeapps/storefront-client';
import { json } from '@shopify/remix-oxygen';
// Recharge session is good for 60 minutes so set to 55 minutes to avoid race conditions
const RECHARGE_SESSION_DURATION = 1000 * 60 * 55;
// loginHelper function
export async function loginRecharge({ hydrogenSession, shopifyStorefrontToken }) {
const customerAccessToken = await hydrogenSession.get('customerAccessToken');
const rechargeSession = await loginShopifyApi(shopifyStorefrontToken, customerAccessToken);
if (rechargeSession) {
const sessionWithExpires = {
...rechargeSession,
expiresAt: Date.now() + RECHARGE_SESSION_DURATION,
};
await hydrogenSession.set('rechargeSession', sessionWithExpires);
} else {
// this should match your catch boundary
throw json('No session created', { status: 400 });
}
return rechargeSession;
}
// helper function for data fetching
export async function rechargeQueryWrapper(rechargeFn, { hydrogenSession, shopifyStorefrontToken }) {
let rechargeSession = await hydrogenSession.get('rechargeSession');
if (!rechargeSession || rechargeSession.expiresAt < Date.now()) {
rechargeSession = await loginRecharge({
hydrogenSession,
shopifyStorefrontToken,
});
}
try {
return await rechargeFn(rechargeSession);
} catch (e) {
try {
if (e?.status === 401) {
// handle auth error, login again and retry request
rechargeSession = await loginRecharge({
hydrogenSession,
shopifyStorefrontToken,
});
return await rechargeFn(rechargeSession);
}
// this should match your catch boundary
throw json(e.message, { status: e?.status });
} catch (error) {
// this should match your catch boundary
throw json(e.message, { status: e?.status });
}
}
}
/app/lib/rechargeUtils.ts
import { loginShopifyApi, Session as RechargeSession } from '@rechargeapps/storefront-client';
import { json } from '@shopify/remix-oxygen';
import { HydrogenSession } from './session.server';
// Recharge session is good for 60 minutes so set to 55 minutes to avoid race conditions
const RECHARGE_SESSION_DURATION = 1000 * 60 * 55;
interface Options {
hydrogenSession: HydrogenSession;
shopifyStorefrontToken: string;
}
// loginHelper function
export async function loginRecharge({ hydrogenSession, shopifyStorefrontToken }: Options): Promise<RechargeSession> {
const customerAccessToken = await hydrogenSession.get('customerAccessToken');
const rechargeSession = await loginShopifyApi(shopifyStorefrontToken, customerAccessToken);
if (rechargeSession) {
const sessionWithExpires = {
...rechargeSession,
expiresAt: Date.now() + RECHARGE_SESSION_DURATION,
};
await hydrogenSession.set('rechargeSession', sessionWithExpires);
} else {
// this should match your catch boundary
throw json('No session created', { status: 400 });
}
return rechargeSession;
}
// helper function for data fetching
export async function rechargeQueryWrapper<T>(
rechargeFn: (session: RechargeSession) => Promise<T>,
{ hydrogenSession, shopifyStorefrontToken }: Options
): Promise<T> {
let rechargeSession = await hydrogenSession.get('rechargeSession');
if (!rechargeSession || rechargeSession.expiresAt < Date.now()) {
rechargeSession = await loginRecharge({
hydrogenSession,
shopifyStorefrontToken,
});
}
try {
return await rechargeFn(rechargeSession);
} catch (e: any) {
try {
if (e?.status === 401) {
// handle auth error, login again and retry request
rechargeSession = await loginRecharge({
hydrogenSession,
shopifyStorefrontToken,
});
return await rechargeFn(rechargeSession);
}
// this should match your catch boundary
throw json(e.message, { status: e?.status });
} catch (error) {
// this should match your catch boundary
throw json(e.message, { status: e?.status });
}
}
}
Login
Now import the loginRecharge
function and call it.
NOTE In this example loginRecharge
updates the session object. Both of the files being altered already commit the session changes as part of their response, but if you are using this somewhere else you will need to make sure you are committing the session changes after calling the loginRecharge
function.
- JavaScript
- TypeScript
/app/routes/($locale).account.login.jsx
& /app/routes/($locale).account.register.jsx
- in action functions after getting a customerAccessToken
import { loginRecharge } from '~/lib/rechargeUtils';
// after `session.set('customerAccessToken', customerAccessToken);`
await loginRecharge({
hydrogenSession: session,
shopifyStorefrontToken: context.env.PUBLIC_STOREFRONT_API_TOKEN,
});
/app/routes/($locale).account_.login.tsx
& /app/routes/($locale).account_.register.tsx
- in action functions after getting a customerAccessToken
Note that filenames may not contain _
in the route name depending on which hydrogen template you chose to use.
import { loginRecharge } from '~/lib/rechargeUtils';
// after `session.set('customerAccessToken', customerAccessToken);`
await loginRecharge({
hydrogenSession: session,
shopifyStorefrontToken: context.env.PUBLIC_STOREFRONT_API_TOKEN,
});
Logout
Make sure you clear the rechargeSession
from your session on logout.
- JavaScript
- TypeScript
/app/routes/($locale).account.logout.jsx
in action
or doLogout
function depending on the template chosen
// right after unsetting the customerAccessToken
session.unset('rechargeSession');
/app/routes/($locale).account_.logout.tsx
in action
or doLogout
function depending on the template chosen
Note that filenames may not contain _
in the route name depending on which hydrogen template you chose to use.
// right after unsetting the customerAccessToken
session.unset('rechargeSession');