Automatically Refreshing Access Tokens
Learn how to automatically refresh access tokens for logged-in users in the Alokai Storefront and Unified SAP Integration
This guide is for Storefronts using SAP Commerce Cloud
If you are using another integration that has a different token renewal process, please refer to the documentation for that integration.
A common challenge in modern frontend applications is the security of the authentication process.
Among the many considerations in the selection process of solutions is the lifespan of a logged-in user's token. Some opt for very restrictive solutions with a short lifespan for the access token. But it's not a great user experience to have to reauthenticate often because your access token expires quickly.
In this guide, we'll implement automatic handling for renewing the access token for requests that fail due to this reason.
Prerequisites
- Alokai Storefront with the configured Middleware SDK Module
- Unified SAP Integration configured in the middleware
Base SDK Configuration
For reference, here is the standard starting configuration of the Alokai Starter's middlewareModule
.
// apps/storefront-unified-nextjs/sdk/sdk.config.ts
import { contentfulModule } from '@vsf-enterprise/contentful-sdk';
import { CreateSdkOptions, createSdk } from '@vue-storefront/next';
import { UnifiedEndpoints } from 'storefront-middleware/types';
if (!process.env.NEXT_PUBLIC_API_BASE_URL) {
throw new Error('NEXT_PUBLIC_API_BASE_URL is required to run the app');
}
const options: CreateSdkOptions = {
middleware: {
apiUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
},
multistore: {
enabled: process.env.NEXT_PUBLIC_MULTISTORE_ENABLED === 'true',
},
};
export const { getSdk } = createSdk(options, ({ buildModule, middlewareModule, middlewareUrl, getRequestHeaders }) => ({
unified: buildModule(middlewareModule<UnifiedEndpoints>, {
apiUrl: `${middlewareUrl}/commerce`,
defaultRequestConfig: {
headers: getRequestHeaders(),
},
}),
cms: buildModule(contentfulModule, {
apiUrl: `${middlewareUrl}/cntf`,
}),
}));
export type Sdk = ReturnType<typeof getSdk>;
Implementing the Token Refreshing Function
At a high level, the process of refreshing the access token involves creating a custom error handler that will attempt to refresh the access token when a request fails due to an unauthorized error.
The @vsf-enterprise/sapcc-sdk
package provides a function that handles the access token renewal process and retrying requests. This function is called createRefreshTokenAndRetryHandler
.
First, install the @vsf-enterprise/sapcc-sdk
package.
yarn add @vsf-enterprise/sapcc-sdk
Next, using the createRefreshTokenAndRetryHandler
function, we can create a refresh token handler and create an error handler.
If the request fails due to an unauthorized error, the error handler will call the refresh token handler to attempt to renew the access token and retry the request.
// apps/storefront-unified-nextjs/sdk/sdk.config.ts
import { contentfulModule } from '@vsf-enterprise/contentful-sdk';
import { createRefreshTokenAndRetryHandler } from '@vsf-enterprise/sapcc-sdk';
import { CreateSdkOptions, createSdk } from '@vue-storefront/next';
import { isSdkUnauthorizedError } from '@vue-storefront/sdk';
import { UnifiedEndpoints } from 'storefront-middleware/types';
if (!process.env.NEXT_PUBLIC_API_BASE_URL) {
throw new Error('NEXT_PUBLIC_API_BASE_URL is required to run the app');
}
const options: CreateSdkOptions = {
middleware: {
apiUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
},
multistore: {
enabled: process.env.NEXT_PUBLIC_MULTISTORE_ENABLED === 'true',
},
};
const handleRefreshTokenAndRetry = createRefreshTokenAndRetryHandler();
export const { getSdk } = createSdk(options, ({ buildModule, middlewareModule, middlewareUrl, getRequestHeaders }) => ({
unified: buildModule(middlewareModule<UnifiedEndpoints>, {
apiUrl: `${middlewareUrl}/commerce`,
defaultRequestConfig: {
headers: getRequestHeaders(),
},
errorHandler: async (context) => {
const { error, params, httpClient, config, url } = context;
return handleRefreshTokenAndRetry(error, () => httpClient(url, params, config), params, {
isUnauthorized(err) {
return isSdkUnauthorizedError(err) && err.message.includes('InvalidTokenError');
},
refreshTokenMethod: async () => {
await httpClient(`${middlewareUrl}/commerce/OAuthUserTokenRefresh`, []);
},
});
},
}),
cms: buildModule(contentfulModule, {
apiUrl: `${middlewareUrl}/cntf`,
}),
}));
export type Sdk = ReturnType<typeof getSdk>;