A lightweight JavaScript library designed to simplify authentication when interacting with the Commerce Layer API. It provides a robust token caching system out-of-the-box, with support for configurable persistent storage, composite storage strategies, and dedicated customer token management.
It works everywhere — on your browser, server, or at the edge.
Commerce Layer is a multi-market commerce API and order management system that lets you add global shopping capabilities to any website, mobile app, chatbot, wearable, voice, or IoT device, with ease. Compose your stack with the best-of-breed tools you already mastered and love. Make any experience shoppable, anywhere, through a blazing-fast, enterprise-grade, and secure API.
To get started with Commerce Layer JS Auth, you need to install it and add it to your project.
Commerce Layer JS Auth is available as an npm package.
# npm
npm install @commercelayer/js-auth
# yarn
yarn add @commercelayer/js-auth
# pnpm
pnpm add @commercelayer/js-auth
# deno
deno add jsr:@commercelayer/js-auth
# bun
bun add @commercelayer/js-authCommerce Layer implements the industry-standard OAuth 2.0 protocol to manage clients' authorization. It defines different types of API credentials. Which one to use depends on your specific needs.
Note
Check our API credentials documentation for further information for each credential type and learn how to create them through the Commerce Layer dashboard.
To use Commerce Layer API you need to be authorized in the first place. This means you need to get a valid access token.
flowchart TB
%% Default style for nodes
classDef node stroke-width:2px;
%% Main nodes
auth(<b>Auth API</b><br/><br/>https://<b>auth</b>.commercelayer.io)
dashboard("JWT Dashboard")
user("JWT User")
sales_channel("JWT Sales Channel")
integration("JWT Integration")
webapp("JWT WebApp")
provisioningAPI(<b>Provisioning API</b><br/><br/>https://<b>provisioning</b>.commercelayer.io)
coreAPI(<b>Core API</b><br/><br/>https://<<b>slug</b>>.commercelayer.io)
metricsAPI(<b>Metrics API</b><br/><br/>https://<<b>slug</b>>.commercelayer.io/metrics)
comingSoon(<b>Metrics API</b> (coming soon)<br/><br/>https://<b>metrics</b>.commercelayer.io)
%% Node styles
style dashboard fill:#FFE6CC,stroke:#D79B00,color:#000
style user fill:#F8CECC,stroke:#B85450,color:#000
style sales_channel fill:#D5E8D4,stroke:#82B366,color:#000
style integration fill:#DAE8FC,stroke:#6C8EBF,color:#000
style webapp fill:#E1D5E7,stroke:#9673A6,color:#000
style comingSoon opacity:.3
%% Connections
auth --> dashboard
auth --> user
auth --> sales_channel
auth --> integration
auth --> webapp
dashboard --> provisioningAPI
user --> provisioningAPI
user --> comingSoon
sales_channel --> coreAPI
integration --> coreAPI
integration --> metricsAPI
webapp --> coreAPI
webapp --> metricsAPI
%% Arrow Styles
linkStyle default stroke-width:2px;
linkStyle 5 stroke:#D79B00
linkStyle 6 stroke:#B85450
linkStyle 7 stroke:#B85450,stroke-dasharray: 5 5,opacity:.3
linkStyle 8 stroke:#82B366
linkStyle 9 stroke:#6C8EBF
linkStyle 10 stroke:#6C8EBF
linkStyle 11 stroke:#9673A6
linkStyle 12 stroke:#9673A6
Authentication calls are subject to rate limiting. To avoid hitting these limits, you should cache the authentication token in a storage (e.g., cookies, Redis, KV). This prevents requesting a new token for each API call.
This library provides a robust token caching system out-of-the-box, with support for any storage solution you choose. It includes tools for composing multiple storage mechanisms (e.g., memory + Redis) to reduce load on the underlying configured storage, and provides dedicated customer token management.
Note
For advanced use cases where you need direct control over token management, you can bypass the built-in caching helpers (makeSalesChannel and makeIntegration) and use the authenticate method directly.
- Single storage — Provides temporary or persistent storage that can survive page reloads. You can implement any storage solution.
- Composite storage — Using the
createCompositeStoragehelper, you can combine multiple storage mechanisms (e.g., memory + Redis) to optimize performance and reduce load on the underlying configured storage. - Customer storage (sales channel only) — Optional dedicated storage for customer authentication tokens, separate from guest tokens.
Here below an example showing a basic setup with an in-memory storage:
import {
makeSalesChannel,
type Storage,
type StorageValue,
} from "@commercelayer/js-auth"
/**
* A valid storage must implement the `Storage` interface
*/
function memoryStorage(): Storage {
const store: Record<string, StorageValue> = {}
return {
async getItem(key) {
return store[key] ?? null
},
async setItem(key: string, value: StorageValue) {
store[key] = value
},
async removeItem(key: string) {
delete store[key]
}
}
}
const salesChannel = makeSalesChannel(
{
clientId: "<your_client_id>",
scope: "market:code:europe",
debug: true,
},
{
storage: memoryStorage(),
},
)
/**
* At the beginning, you'll get a guest token
*/
const authorization1 = await salesChannel.getAuthorization()
console.log("Guest access token:", authorization1.accessToken)
/**
* For consecutive calls, you'll get the previous guest token
* from the storage or a new one if expired
*/
const authorization2 = await salesChannel.getAuthorization()
console.log("Customer access token:", authorization2.accessToken)The following flowchart illustrates how the library manages token caching, validation, and refresh flow:
flowchart TB
%% Default style for nodes
classDef node stroke-width:2px,color:#000;
GetAuthorization(".getAuthorization()") --> CheckCustomerStorage{"Check <b>Customer</b><br/>in storage"}
CheckCustomerStorage -->|Found| ValidateCustomerToken{"Is token<br/>still valid?"}
CheckCustomerStorage -->|Not Found| CheckGuestStorage{"Check <b>Guest</b><br/>in storage"}
ValidateCustomerToken -->|Yes| ReturnCustomerToken(["Return <b>Customer</b> Token"])
ValidateCustomerToken -->|No| HasRefreshToken{"<b>Customer</b> has<br/>refresh token?"}
HasRefreshToken -->|Yes| RefreshToken["Refresh <b>Customer</b> token"]
HasRefreshToken -->|No| CheckGuestStorage
RefreshToken --> StoreNewToken["Store new <b>Customer</b> token"]
StoreNewToken --> ReturnCustomerToken
%% CheckGuestMemory -->|Found| ValidateGuestToken{"Is token<br/>still valid?"}
%% CheckGuestMemory -->|Not Found| CheckGuestStorage{"Check <b>Guest</b><br/>in storage"}
ValidateGuestToken -->|Yes| ReturnGuestToken(["Return <b>Guest</b> token"])
ValidateGuestToken -->|No| CreateNewGuest["Create new <b>Guest</b> token"]
CheckGuestStorage -->|Found| ValidateGuestToken{"Is token<br/>still valid?"}
CheckGuestStorage -->|Not Found| CreateNewGuest
CreateNewGuest --> StoreNewGuestToken["Store new <b>Guest</b> token"]
StoreNewGuestToken --> ReturnGuestToken
classDef process fill:#DAE8FC,stroke:#6C8EBF
classDef condition fill:#FFE6CC,stroke:#D79B00
classDef code fill:#FFF,stroke:#000,font-family:monospace
classDef startState fill:#FFF,stroke:#000
classDef endState fill:#FFF,stroke:#000
class GetAuthorization code;
class Start startState;
class CheckCustomerMemory,CheckCustomerStorage,CheckGuestMemory,CheckGuestStorage condition;
class ValidateCustomerToken,ValidateGuestToken,HasRefreshToken condition;
class RefreshToken,StoreCustomerTokenInMemory,CreateNewGuest,StoreNewToken,StoreNewGuestToken process;
class ReturnCustomerToken,ReturnGuestToken endState;
Sales channels are used to build any customer touchpoint (e.g. your storefront with a fully-functional shopping cart and checkout flow).
Below is a complete example showing how to use a sales channel for both guest and customer authentication:
import { authenticate, makeSalesChannel } from "@commercelayer/js-auth"
// The `Storage` interface is fully-compatible with the `unstorage` library.
import { createStorage } from "unstorage"
import localStorageDriver from "unstorage/drivers/localstorage"
const salesChannel = makeSalesChannel(
{
clientId: "<your_client_id>",
scope: "market:code:europe",
},
{
/**
* You can use any storage implementation you prefer or implement your own.
* In this example we use `unstorage` with the `localStorage` driver
*/
storage: createStorage({
driver: localStorageDriver({}),
}),
},
)
/**
* Get the current authorization state which includes the access token.
* This method handles caching and token refresh automatically.
*/
const guestAuthorization = await salesChannel.getAuthorization()
console.log("Guest access token:", guestAuthorization.accessToken)
/**
* Authenticate a customer using their email and password, or
* though the JWT bearer flow.
*/
const customerCredentials = await authenticate("password", {
clientId: "<your_client_id>",
scope: "market:code:europe",
username: "john@example.com",
password: "secret",
})
/**
* Set the customer credentials in the storage.
*/
await salesChannel.setCustomer({
accessToken: customerCredentials.accessToken,
scope: customerCredentials.scope,
/**
* When `refreshToken` is provided, it'll be used to automatically
* refresh the customer access token when it expires.
*/
refreshToken: customerCredentials.refreshToken,
})
/**
* Get the current customer authorization.
*/
const customerAuthorization = await salesChannel.getAuthorization()
console.log("Customer access token:", customerAuthorization.accessToken)
/**
* Logout the current customer.
* This will remove the customer authorization from the storage, and revoke the access token.
*/
await salesChannel.logoutCustomer()Customer authentication is supported through two OAuth 2.0 grant types: password and JWT bearer. Both flows return an accessToken, scope, and refreshToken that can be stored using the setCustomer method.
Sales channels can use the password grant type to exchange customer credentials for an access token (i.e., to get a "logged" access token).
import { authenticate } from "@commercelayer/js-auth"
const auth = await authenticate("password", {
clientId: "<your_client_id>",
scope: "market:code:europe"
username: "john@example.com",
password: "secret"
})
console.log("My access token:", auth.accessToken)
console.log("Expiration date:", auth.expires)
console.log("My refresh token:", auth.refreshToken)Sales channels can use the refresh token grant type to refresh a customer access token with a "remember me" option:
import { authenticate } from "@commercelayer/js-auth"
const newToken = await authenticate("refresh_token", {
clientId: "<your_client_id>",
scope: "market:code:europe"
refreshToken: "<your_refresh_token>"
})Note
When using the makeSalesChannel helper, token refresh is handled automatically. The helper monitors token expiration and seamlessly refreshes customer tokens in the background, eliminating the need for manual refresh calls.
Commerce Layer supports OAuth 2.0 JWT Bearer token exchange, enabling applications to obtain access tokens by exchanging JWT assertions. This is particularly useful for implementing delegated authentication flows, where an application needs to make API calls on behalf of a customer without requiring their direct interaction. The flow consists of two steps:
-
Creating a signed JWT assertion containing the customer's claims
const assertion = await createAssertion({ payload: { "https://commercelayer.io/claims": { owner: { type: "Customer", id: "4tepftJsT2" }, custom_claim: { customer: { first_name: "John", last_name: "Doe" } } } } })
-
Exchanging this assertion for an access token
import { authenticate } from "@commercelayer/js-auth" const auth = await authenticate("urn:ietf:params:oauth:grant-type:jwt-bearer", { clientId: "<your_client_id>", clientSecret: "<your_client_secret>", scope: "market:code:europe" assertion }) console.log("My access token:", auth.accessToken) console.log("Expiration date:", auth.expires) console.log("My refresh token:", auth.refreshToken)
Both sales channels and webapps can use this JWT bearer flow to implement secure delegated authentication.
Integrations are used to develop backend integrations with any 3rd-party system.
import {
createCompositeStorage,
makeIntegration,
} from "@commercelayer/js-auth"
// The `Storage` interface is fully-compatible with the `unstorage` library.
import { createStorage } from "unstorage"
import memoryDriver from "unstorage/drivers/memory"
import redisDriver from "unstorage/drivers/redis"
const memoryStorage = createStorage({
driver: memoryDriver(),
})
const redisStorage = createStorage({
driver: redisDriver({
url: "<your_redis_connection_string>",
}),
})
const compositeStorage = createCompositeStorage([
memoryStorage,
redisStorage,
])
const integration = makeIntegration(
{
clientId: "<your_client_id>",
clientSecret: "<your_client_secret>",
debug: true,
},
{
storage: compositeStorage,
},
)
/**
* If you already requested an access token before, now you'll probably get it from Redis if not expired.
* Otherwise, a new one will be requested.
*
* This method handles caching and token refresh automatically.
*/
const authorization1 = await integration.getAuthorization()
console.log("Integration access token #1:", authorization1.accessToken)
/**
* Subsequent calls will return the cached token from memory storage.
*/
const authorization2 = await integration.getAuthorization()
console.log("Integration access token #2:", authorization2.accessToken)
/**
* Revoke the current integration authorization.
* This will remove the authorization from memory and storage, and revoke the access token.
*/
await integration.revokeAuthorization()
/**
* Disposes all mounted storages to ensure there are no open-handles left.
* Call it before exiting process.
*/
await compositeStorage.dispose?.()Available only for browser applications
Webapps use the authorization code grant type to exchange an authorization code for an access token.
In this case, first, you need to get an authorization code, then you can exchange it with an access token:
-
Create a webapp on Commerce Layer and take note of the API credentials (client ID, client secret, callback URL, base endpoint, and the ID of the market you want to put in scope)
-
Use this URL to authorize your webapp on Commerce Layer:
https://dashboard.commercelayer.io/oauth/authorize?client_id={{your_client_id}}&redirect_uri={{your_redirect_uri}}&scope=market:id:xYZkjABcde&response_type=code&state=1a2b3c-
Once you've authorized the webapp, you will be redirected to the callback URL:
Use this code to get the access token:
import { authenticate } from "@commercelayer/js-auth"
const auth = await authenticate("authorization_code", {
clientId: "<your_client_id>",
clientSecret: "<your_client_secret>",
callbackUrl: "<https://yourdomain.com/callback>",
code: "<your_auth_code>"
})
console.log("My access token: ", auth.accessToken)
console.log("Expiration date: ", auth.expires)Provisioning applications use the client credentials grant type to get an access token.
-
Access your personal provisioning application on Commerce Layer dashboard and take note of your Provisioning API credentials (client ID, client secret)
-
Use this code to get the access token:
import { authenticate } from "@commercelayer/js-auth"
const auth = await authenticate("client_credentials", {
clientId: "<your_client_id>",
clientSecret: "<your_client_secret>"
})
console.log("My access token: ", auth.accessToken)
console.log("Expiration date: ", auth.expires)For advanced use cases where you need direct control over token management, you can bypass the built-in caching helpers (makeSalesChannel and makeIntegration) and use the authenticate method directly.
Warning
Note that, in this case, you'll be responsible for implementing your own token caching and refresh logic.
Sales channel applications use the client credentials grant type to get a "guest" access token.
import { authenticate } from "@commercelayer/js-auth"
const auth = await authenticate("client_credentials", {
clientId: "<your_client_id>",
scope: "market:code:europe"
})
console.log("My access token: ", auth.accessToken)
console.log("Expiration date: ", auth.expires)Integration applications use the client credentials grant type to get an access token for themselves.
import { authenticate } from "@commercelayer/js-auth"
const auth = await authenticate("client_credentials", {
clientId: "<your_client_id>",
clientSecret: "<your_client_secret>",
})
console.log("My access token: ", auth.accessToken)
console.log("Expiration date: ", auth.expires)Any previously generated access tokens (refresh tokens included) can be revoked before their natural expiration date:
import { revoke } from "@commercelayer/js-auth"
await revoke({
clientId: "<your_client_id>",
clientSecret: "<your_client_secret>",
token: "<a_generated_access_token>"
})We offer a helper method to decode an access token. The return is fully typed.
Important
You should not use this for untrusted messages, since this helper method does not verify whether the signature is valid. If you need to verify the access token before decoding, you can use jwtVerify instead.
import { authenticate, jwtDecode, jwtIsSalesChannel } from "@commercelayer/js-auth"
const auth = await authenticate("client_credentials", {
clientId: "<your_client_id>",
scope: "market:code:europe"
})
const decodedJWT = jwtDecode(auth.accessToken)
if (jwtIsSalesChannel(decodedJWT.payload)) {
console.log("organization slug is", decodedJWT.payload.organization.slug)
}We offer an helper method to verify an access token.
It validates the integrity and authenticity of the JWT. It checks if the token is valid by verifying the signature against the public key used to create it.
This is useful to ensure that the token hasn't been tampered with and originates from Commerce Layer.
The return is fully typed:
import { authenticate, jwtVerify, jwtIsSalesChannel } from "@commercelayer/js-auth"
const auth = await authenticate("client_credentials", {
clientId: "<your_client_id>",
scope: "market:code:europe"
})
const decodedJWT = await jwtVerify(auth.accessToken, {
ignoreExpiration: true
})
if (jwtIsSalesChannel(decodedJWT.payload)) {
console.log("organization slug is", decodedJWT.payload.organization.slug)
}Derive the Core API base endpoint given a valid access token.
import { getCoreApiBaseEndpoint } from "@commercelayer/js-auth"
getCoreApiBaseEndpoint("<a_valid_access_token>") //= "https://yourdomain.commercelayer.io"The method requires a valid access token with an organization in the payload. When the organization is not set (e.g., provisioning token), it throws an InvalidTokenError exception.
It returns the Provisioning API base endpoint given a valid access token.
import { getProvisioningApiBaseEndpoint } from "@commercelayer/js-auth"
getProvisioningApiBaseEndpoint("<a_valid_access_token>") //= "https://provisioning.commercelayer.io"The method requires a valid access token (the token can be used with Provisioning API). When the token is not valid (e.g., core api token), it throws an InvalidTokenError exception.
-
Fork this repository (learn how to do this here).
-
Clone the forked repository like so:
git clone https://github.com/<your username>/commercelayer-js-auth.git && cd commercelayer-js-auth-
Make your changes and create a pull request (learn how to do this).
-
Someone will attend to your pull request and provide some feedback.
- Join Commerce Layer's Discord community.
- Ping us on Bluesky, X, or LinkedIn.
- Is there a bug? Create an issue on this repository.
This repository is published under the MIT license.
