# Installation
# Prerequisites
Before you start, make sure to have commercetools and Auth0 accounts and working Vue Storefront 2 project.
# Manual installation
There are several steps required to install this integration manually. The whole process can be split into three parts:
- Configuration of the commercetools project.
- Configuration of the Auth0 tenant.
- Installation in Vue Storefront 2 project.
# Configure commercetools project
# Create API client for Auth0 Actions
In commercetools Merchant Center, open the Settings > Developer settings
page and create a new API client with the following permissions selected:
- Manage > Customers,
- Manage > Orders,
- View > Orders.
Click the "Create API client" button and save the credentials for later.
# Enable introspection
Before enabling introspection, we need to generate a long and random string. It will be included in the requests and used to verify if commercetools servers initiated the introspection.
You can use any randomly generated string. One way is to use the following command on systems with openssl
installed:
openssl rand -hex 32
Save this string for later.
Go to commercetools ImpEx (opens new window) and log in using your commercetools credentials. Once logged in, open the GraphQL IDE
page and select a proper project from the dropdown.
With proper project selected execute the following query:
query project {
project {
version
}
}
Copy the version number and use it to execute the next query:
mutation UpdateExternalOAuth {
project: updateProject(version: <VERSION>, actions: [ # Change <VERSION> to the number from previous query
{
setExternalOAuth: {
externalOAuth: {
url: "<URL>/api/auth0/introspect", # Change <URL> to the public-facing, always available environment, that will be used to introspect for all environments.
authorizationHeader: "<HEADER>" # Use the random string generated before. Keep it for later.
}
}
}
]) {
externalOAuth {
url
authorizationHeader
}
}
}
# Configure Auth0 tenant
# Create new tenant
Create a new tenant in Auth0. Select a region close to the one used in commercetools and your Vue Storefront servers. Close location is essential because, on every first request made using a given access token, commercetools will send a token introspection (opens new window) request to the Vue Storefront application. If it doesn't receive a response in a concise time window (500 milliseconds by default), the request will fail.
# Create new application
Go to the Applications > Applications
page and create a new Application with the type Regular Web Application
. Once created, go to the Settings
tab and update the following fields:
in the
Allowed Callback URLs
field add URLs to all environments (includinglocalhost
) followed by/api/auth0/callback/
. Example:in the
Allowed Logout URLs
field add URLs to all environments (includinglocalhost
) followed by/api/auth0/postLogout
. Example:in the
Allowed Web Origins
field add URLs to all environments (includinglocalhost
). Example:
Save the changes.
Callback URL should end with a slash
Note that slash (/
) at the end of each callback URL defined in Allowed Callback URLs
is required due to the bug reported on Auth0 forums (opens new window).
Your configuration might look like this:
# Allowed Callback URLs
http://localhost:3000/api/auth0/callback/, https://example.com/api/auth0/callback/
# Allowed Logout URLs
http://localhost:3000/api/auth0/postLogout, https://example.com/api/auth0/postLogout
# Allowed Web Origins
http://localhost:3000, https://example.com
# Create new API
Go to the Applications > APIs
page and create a new API with an identifier https://commercetools.com/
. Once created, go to the "Permissions" tab and add all commercetools scopes you would like the customers to have when they log in. Refer to commercetools Scopes documentation (opens new window) for a list of available scopes.
For example, you can define the following scopes:
Permission | Description |
---|---|
create_anonymous_token:<PROJECT_NAME> | Create anynoymous token |
manage_my_profile:<PROJECT_NAME> | Manage user profile |
view_categories:<PROJECT_NAME> | View product categories |
manage_my_payments:<PROJECT_NAME> | Manage user payments |
manage_my_orders:<PROJECT_NAME> | Manage user orders |
manage_my_shopping_lists:<PROJECT_NAME> | Manage user shopping lists |
view_published_products:<PROJECT_NAME> | View published products |
view_stores:<PROJECT_NAME> | View stores |
# Create Action to register users in commercetools
Open the Actions > Custom Actions
and click on the "Create" button. Add an Action with the name "Register in CT on the first login" and "Login / Post Login" trigger.
Add new secrets by clicking the key icon on the left side. Most of them was generated in Create API client for Auth0 Actions section:
PROJECT_KEY
CLIENT_ID
CLIENT_SECRET
API_HOST
AUTH_HOST
-https://auth.sphere.io
.
Then, add modules by clicking the package icon on the left side:
- Name:
@commercetools/sdk-client
, version:2.1.2
- Name:
@commercetools/sdk-middleware-auth
, version:6.1.4
- Name:
@commercetools/sdk-middleware-http
, version:6.0.11
- Name:
node-fetch
, version:2.6.1
The next step is to replace the code in the editor with the following:
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
if (event.user.app_metadata.commercetools_user_id) {
return;
}
const crypto = require("crypto");
const fetch = require('node-fetch');
const { createClient } = require('@commercetools/sdk-client');
const { createHttpMiddleware } = require('@commercetools/sdk-middleware-http');
const { createAuthMiddlewareForClientCredentialsFlow } = require('@commercetools/sdk-middleware-auth');
const projectKey = event.secrets.PROJECT_KEY;
const authMiddleware = createAuthMiddlewareForClientCredentialsFlow({
host: event.secrets.AUTH_HOST,
projectKey,
credentials: {
clientId: event.secrets.CLIENT_ID,
clientSecret: event.secrets.CLIENT_SECRET,
},
scopes: [`manage_customers:${projectKey}`],
fetch
});
const httpMiddleware = createHttpMiddleware({
host: event.secrets.API_HOST,
fetch
});
const client = createClient({
middlewares: [authMiddleware, httpMiddleware],
});
const response = await client.execute({
uri: `/${projectKey}/customers`,
method: 'POST',
body: {
email: event.user.email,
password: crypto.randomBytes(64).toString('base64'),
externalId: event.user.user_id
}
});
api.user.setAppMetadata('commercetools_user_id', response.body.customer.id);
};
/**
* Handler that will be invoked when this action is resuming after an external redirect. If your
* onExecutePostLogin function does not perform a redirect, this function can be safely ignored.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
// exports.onContinuePostLogin = async (event, api) => {
// };
Click the "Save draft" and "Deploy" buttons.
# Create Action to merge guest and user carts after login
Open the Actions > Custom Actions
page and click on the "Create" button and add an Action with the name "Merge carts" and "Login / Post Login" trigger.
Add new secrets by clicking the key icon on the left side. Most of them was generated in Create API client for Auth0 Actions section:
PROJECT_KEY
CLIENT_ID
CLIENT_SECRET
API_HOST
AUTH_HOST
-https://auth.sphere.io
.
Then, add modules by clicking the package icon on the left side:
- Name:
@commercetools/api-request-builder
, version:5.6.3
. - Name:
@commercetools/sdk-client
, version:2.1.2
. - Name:
@commercetools/sdk-middleware-auth
, version:6.1.4
. - Name:
@commercetools/sdk-middleware-http
, version:6.0.11
. - Name:
node-fetch
, version:2.6.1
.
The next step is to replace the code in the editor with the following:
const fetch = require('node-fetch');
const { createClient } = require('@commercetools/sdk-client');
const { createHttpMiddleware } = require('@commercetools/sdk-middleware-http');
const { createAuthMiddlewareForClientCredentialsFlow } = require('@commercetools/sdk-middleware-auth');
const { createRequestBuilder } = require('@commercetools/api-request-builder');
async function getGuestCart(client, projectKey, anonymousId) {
const uri = createRequestBuilder({ projectKey })
.carts
.where(`anonymousId = "${anonymousId}"`)
.perPage(1)
.page(1)
.build();
const response = await client.execute({
uri,
method: 'GET',
});
return response.body.results[0];
}
async function getUserCart(client, projectKey, commercetoolsUserId, currencyCode) {
const uri = createRequestBuilder({ projectKey })
.carts
.where(`customerId = "${commercetoolsUserId}"`, `totalPrice.currencyCode = "${currencyCode}"`)
.parse({
sort: [
{ by: 'lastModifiedAt', direction: 'desc' }
]
})
.perPage(1)
.page(1)
.build();
const response = await client.execute({
uri,
method: 'GET',
});
return response.body.results[0];
}
async function mergeCarts(client, projectKey, guestCart, userCart) {
const actions = guestCart.lineItems.map(lineItem => ({
action: 'addLineItem',
quantity: lineItem.quantity,
sku: lineItem.variant.sku
}));
const response = await client.execute({
uri: `/${projectKey}/carts/${userCart.id}`,
method: 'POST',
body: {
version: userCart.version,
actions
}
});
return response.body;
}
async function assignCartToUser(client, projectKey, guestCart, commercetoolsUserId) {
const response = await client.execute({
uri: `/${projectKey}/carts/${guestCart.id}`,
method: 'POST',
body: {
version: guestCart.version,
actions: [
{
action: 'setCustomerId',
customerId: commercetoolsUserId
}
]
}
});
return response.body;
}
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
const anonymousId = event.request.query.anonymousId;
if (!anonymousId) {
return;
}
const projectKey = event.secrets.PROJECT_KEY;
const authMiddleware = createAuthMiddlewareForClientCredentialsFlow({
host: event.secrets.AUTH_HOST,
projectKey,
credentials: {
clientId: event.secrets.CLIENT_ID,
clientSecret: event.secrets.CLIENT_SECRET,
},
scopes: [
`view_orders:${projectKey}`,
`manage_orders:${projectKey}`
],
fetch
});
const httpMiddleware = createHttpMiddleware({
host: event.secrets.API_HOST,
fetch
});
const client = createClient({
middlewares: [authMiddleware, httpMiddleware],
});
const guestCart = await getGuestCart(
client,
projectKey,
anonymousId
);
if (!guestCart.lineItems.length) {
// Guest cart is empty
return;
}
const userCart = await getUserCart(
client,
projectKey,
event.user.app_metadata.commercetools_user_id,
guestCart.totalPrice.currencyCode
)
if (!userCart) {
// Assign guestCart to user if he has no cart whose currency matches the anonymous cart
// or has no cart at all
return await assignCartToUser(
client,
projectKey,
guestCart,
event.user.app_metadata.commercetools_user_id
);
}
// Merge carts if user has a cart already
await mergeCarts(client, projectKey, guestCart, userCart)
};
/**
* Handler that will be invoked when this action is resuming after an external redirect. If your
* onExecutePostLogin function does not perform a redirect, this function can be safely ignored.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
// exports.onContinuePostLogin = async (event, api) => {
// };
Click the "Save draft" and "Deploy" buttons.
# Create Action to add user-specific claims
Open the Actions > Custom Actions
page and click on the "Create" button and add an Action with the name "Add user-specific claims" and "Login / Post Login" trigger.
Replace the code in the editor with the following:
exports.onExecutePostLogin = async (event, api) => {
api.accessToken.setCustomClaim('https://commercetools.com/customer_id', event.user.app_metadata.commercetools_user_id);
};
Click the "Save draft" and "Deploy" buttons.
# Update Login flow
Open Actions > Flows > Login
page. Drag in newly created Actions from the right side to create the following flow:
Start
↓
Register in CT on the first login
↓
Merge carts
↓
Add user-specific claims
↓
Complete
Click the "Apply" button.
# Install in Vue Storefront 2 project
# Install the package
Install the integration package from our private registry. Please refer to How to use Vue Storefront Enterprise (opens new window) document if you are not already logged in:
yarn add @vsf-enterprise/ct-auth0
# Update imports
Auth0 integration package ships with a single useAuth0
composable. The next step is to replace the login
, register
, and logout
methods imported from useUser
with their equivalents imported from useAuth0
.
import { useUser } from '@vue-storefront/commercetools';
+ import { useAuth0 } from '@vsf-enterprise/ct-auth0';
export default {
setup () {
- const { login, register, logout, user, load, setUser } = useUser();
+ const { user, load, setUser } = useUser();
+ const { login, register, logout } = useAuth0;
}
}
# Update Nuxt configuration
Open the nuxt.config.js
and:
- Add
['@vsf-enterprise/ct-auth0/nuxt', {}]
tobuildModules
array. - Add
@vsf-enterprise/ct-auth0
todev
andprod
inuseRawSource
in@vue-storefront/nuxt
module.
# Update "My Account" page
In pages/MyAccount.vue
, remove is-authenticated
from the middleware
and replace it with the isAuthenticated middleware:
// pages/MyAccount.vue
import { isAuthenticated } from '@vsf-enterprise/ct-auth0';
middleware: [
isAuthenticated
]
Then, remove the "Password change" tab from "My account > My profile".
# Update Server Middleware configuration
Open the middleware.config.js
file and add configuration for auth0
. Below is the minimal configuration required to make this package working. Refer to the Configuration interface for more details.
// middleware.config.js
const { tokenExtension } = require('@vsf-enterprise/ct-auth0/extensions');
const appURL = process.env.NODE_ENV === "production"
? '' // Test or staging environment URL. Should NOT end with "/"
: 'http://localhost:3000';
module.exports = {
integrations: {
ct: {
location: '@vue-storefront/commercetools-api/server',
extensions: existing => existing.concat(
// other extensions
tokenExtension
),
},
auth0: {
location: '@vsf-enterprise/ct-auth0/server',
configuration: {
api: {
appURL,
authorizationHeader: '', // String generated in the "Enable introspection" section
}
oidc: {
baseURL: `${ appURL }/api`,
issuerBaseURL: '<URL>', // "Domain" from "Applications > Applications>"
clientID: '<ID>', // "Client ID" from "Applications > Applications>"
clientSecret: '<SECRET>', // "Client Secret" from "Applications > Applications>"
secret: '', // Randomly generated string
authorizationParams: {
audience: 'https://commercetools.com/',
scope: [
'openid',
// List of customer scopes defined in the "Create new API" section
].join(' ')
}
}
}
}
}
};
# Deploy the changes
Deploy your changes to the environment configured in the Enable introspection section. Otherwise, the commercetools server won't be able to introspect the access tokens included in Vue Storefront requests and throw errors.