Invoke OpenAPI-based AEM APIs with user authentication from a web app invoke-openapi-based-aem-apis-from-web-app
Learn how to invoke OpenAPI-based AEM APIs on AEM as a Cloud Service using user-based authentication from an external web app with OAuth Web App authentication.
The OAuth Web App authentication is ideal for web applications with frontend and backend components that need to access AEM APIs on behalf of a user. It uses the OAuth 2.0 authorization_code grant type to obtain an access token on behalf of the user to access the AEM APIs. For more information, see Difference between OAuth Server-to-Server and OAuth Web App/Single Page App credentials
Sample web app: WKND-PIM overview and functional flow
Before you start, let’s understand the sample web app, WKND Product Information Management (PIM), and its functional flow.
The WKND PIM app is a sample web application designed to manage product attributes and its asset metadata stored within AEM as a Cloud Service. This example demonstrates how web apps can seamlessly integrate with Adobe APIs to deliver efficient, user-centric workflows.
The Adobe Developer Console (ADC) project is configured to access the Assets Author API using the OAuth Web App authentication. It provides the necessary client_id and client_secret to the WKND-PIM web app to initiate the authorization_code grant flow.
The following diagram illustrates the functional flow of the WKND-PIM web app getting user-specific access tokens to interact with the Assets Author API.
- The web app initiates the process by redirecting the user to the Adobe Identity Management System (IMS) for authentication.
- Along with the redirect, the web app passes the required client_id and redirect_uri to the IMS.
- IMS authenticates the user and sends them back to the specified redirect_uri with an authorization_code.
- The web app exchanges the authorization_code with IMS for a user-specific access token, using its client_id and client_secret.
- Upon successful validation, IMS issues the user-specific access token.
- The web app securely uses the access token to interact with the Assets Author API, enabling the user to retrieve or update product asset metadata.
The WKND-PIM web app is developed using Node.js and Express. Express acts as the server that securely manages private secrets and user-specific access tokens.
Other web stacks (Java, Python, .NET-based, etc.) can be used to create web apps that integrate with the Adobe APIs using the approaches illustrated in this tutorial.
What you learn what-you-learn
In this tutorial, you learn how to:
-
Create and configure an Adobe Developer Console (ADC) project to access OpenAPI-based AEM APIs using OAuth Web App authentication.
-
Implement the OAuth Web App authentication flow in a custom web app.
- IMS user authentication and app authorization.
- User-specific access token retrieval.
- Accessing OpenAPI-based AEM APIs using the user-specific access token.
Before you start, make sure you reviewed the Accessing Adobe APIs and related concepts section.
How to use this tutorial how-to-use-this-tutorial
You can either Review web app key code snippets section to understand the OAuth Web App authentication flow and API calls code snippets used in the WKND-PIM web app. Or directly proceed to the Setup and run web app section to set up and run the WKND-PIM web app on your local machine.
Review web app key code snippets review-web-app-key-code-snippets
Let’s review the key code snippets used in the WKND-PIM web app to understand the OAuth Web App authentication flow and API calls.
Download the WKND-PIM web app code
-
Download the WKND-PIM web app zip file and extract it.
-
Navigate to the extracted folder and open the
.env.example
file in your favorite code editor. Review the required configuration parameters.code language-plaintext ######################################################################## # Adobe IMS, Adobe Developer Console (ADC), and AEM Assets Information ######################################################################## # Adobe IMS OAuth endpoints ADOBE_IMS_AUTHORIZATION_ENDPOINT=https://ims-na1.adobelogin.com/ims/authorize/v2 ADOBE_IMS_TOKEN_ENDPOINT=https://ims-na1.adobelogin.com/ims/token/v3 ADOBE_IMS_USERINFO_ENDPOINT=https://ims-na1.adobelogin.com/ims/userinfo/v2 # Adobe Developer Console (ADC) Project's OAuth Web App credential ADC_CLIENT_ID=<ADC Project OAuth Server-to-Server credential ClientID> ADC_CLIENT_SECRET=<ADC Project OAuth Server-to-Server credential Client Secret> ADC_SCOPES=<ADC Project OAuth Server-to-Server credential Scopes> # AEM Assets Information AEM_ASSET_HOSTNAME=<AEM Assets Hostname, e.g., https://author-p63947-e1502138.adobeaemcloud.com/> AEM_ASSET_IDS=< AEM Asset IDs Comma Seperated, e.g., urn:aaid:aem:9f20a8ce-934a-4560-8720-250e529fbb17,urn:aaid:aem:6e0123cd-8a67-4d1f-b721-1b3da987d831> ################################################ # Web App Information ################################################ # The port number on which this server (web app) will run PORT = 3000 # The URL to which the user will be redirected after the OAuth flow is complete REDIRECT_URI=https://localhost:3001/callback # The Express (express-session) uses this secret to encrypt and verify the authenticity of that cookie EXPRESS_SESSION_SECRET=<Express Session Secret>
You need to replace the placeholders with the actual values from the Adobe Developer Console (ADC) Project and AEM as a Cloud Service Assets instance.
IMS user authentication and app authorization
Let’s review the code that initiates the IMS user authentication and app authorization. To review or update the asset metadata, the user must authenticate against the Adobe IMS and authorize the WKND-PIM web app to access the Assets Author API on their behalf.
On the very first login attempt, the user must provide the consent to allow the WKND-PIM web app to access the Assets Author API on their behalf.
-
The
routes/update-product-attributes.js
file verifies if the user’s Express session has an access token. If not, it redirects the user to the/auth
route.code language-javascript ... // The update-product-attributes route, shows the product attributes form with tabs router.get("/update-product-attributes", async (req, res) => { // Check if the user is authenticated, if not redirect to the auth route if (!req.session.accessToken) { return res.redirect("/auth"); } ... });
-
In
routes/adobe-ims-auth.js
file, the/auth
route initiates the IMS user authentication and app authorization flow. Note the client_id, redirect_uri, and response_type parameters passed to the Adobe IMS authorization endpoint.code language-javascript ... // Route to initiate Adobe IMS user authentication router.get("/auth", (req, res) => { // Redirect user to Adobe IMS authorization endpoint try { // Constructing the authorization URL const params = new URLSearchParams({ client_id: adobeADCConfig.clientId, redirect_uri: redirectUri, response_type: "code", }); // Append scopes if defined in configuration if (adobeADCConfig?.scopes) params.append("scope", adobeADCConfig.scopes); // Redirect user to Adobe IMS authorization URL const imsAuthorizationUrl = `${ adobeIMSConfig.authorizationEndpoint }?${params.toString()}`; res.redirect(imsAuthorizationUrl); } catch (error) { console.error("Error initiating Adobe IMS authentication:", error); res.status(500).send("Unable to initiate authentication"); } }); ...
If the user is not authenticated against the Adobe IMS, the Adobe ID login page is displayed asking the user to authenticate.
If already authenticated, the user is redirected back to the specified redirect_uri of the WKND-PIM web app with an authorization_code.
Access token retrieval
The WKND-PIM web app securely exchanges the authorization_code with the Adobe IMS for a user-specific access token using the client_id and client_secret of the ADC Project’s OAuth Web App credential.
In the routes/adobe-ims-auth.js
file, the /callback
route exchanges the authorization_code with the Adobe IMS for a user-specific access token.
...
// Callback route to exchange authorization code for access token
router.get("/callback", async (req, res) => {
// Extracting authorization code from the query parameters
const authorizationCode = req.query.code;
if (!authorizationCode) {
return res.status(400).send("Missing authorization code");
}
// Exchange authorization code for access token
try {
// Fetch access token from Adobe IMS token endpoint
const response = await fetch(adobeIMSConfig.tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(
`${adobeADCConfig.clientId}:${adobeADCConfig.clientSecret}`
).toString("base64")}`,
},
body: new URLSearchParams({
code: authorizationCode,
grant_type: "authorization_code",
}),
});
if (!response.ok) {
console.error("Failed to fetch access token:", response.statusText);
return res.status(500).send("Failed to fetch access token");
}
const data = await response.json();
if (!data.access_token) {
console.error("Access token missing in the response:", data);
return res.status(500).send("Invalid response from token endpoint");
}
// For debugging purposes
console.log("Access token:", data.access_token);
// Store the access token in the session
req.session.accessToken = data.access_token;
// Redirect user to update product attributes
res.redirect("/update-product-attributes");
} catch (error) {
console.error("Error exchanging authorization code:", error);
res.status(500).send("Error during token exchange");
}
});
The access token is stored in the Express session for subsequent requests to the Assets Author API.
Accessing OpenAPI-based AEM APIs using the access token
The WKND-PIM web app securely uses the user-specific access token to interact with the Assets Author API, enabling the user to retrieve or update product asset metadata.
In the routes/invoke-aem-apis.js
file, the /api/getAEMAssetMetadata
and /api/updateAEMAssetMetadata
routes invoke the Assets Author APIs using the access token.
...
// API Route: Get AEM Asset Metadata
router.get("/api/getAEMAssetMetadata", async (req, res) => {
const assetId = req.query.assetId;
const bucketName = getBucketName(aemAssetsConfig.hostname);
if (!assetId || !bucketName) {
return res.status(400).json({ error: "Missing AEM Information" });
}
// Get the access token from the session
const accessToken = req.session.accessToken;
if (!accessToken) {
return res.status(401).json({ error: "Not Authenticated with Adobe IMS" });
}
try {
const assetMetadata = await invokeGetAssetMetadataAPI(
bucketName,
assetId,
accessToken
);
const filteredMetadata = getFilteredMetadata(JSON.parse(assetMetadata));
res.status(200).json(filteredMetadata);
} catch (error) {
console.error("Error getting asset metadata:", error.message);
res.status(500).json({ error: `Internal Server Error: ${error.message}` });
}
});
// Helper function to invoke the AEM API to get asset metadata
async function invokeGetAssetMetadataAPI(bucketName, assetId, accessToken) {
const apiUrl = `https://${bucketName}.adobeaemcloud.com/adobe/assets/${assetId}/metadata`;
// For debugging purposes
console.log("API URL:", apiUrl);
console.log("Access Token:", accessToken);
console.log("API Key:", adobeADCConfig.clientId);
try {
const response = await fetch(apiUrl, {
method: "GET",
headers: {
"If-None-Match": "string",
"X-Adobe-Accept-Experimental": "1",
Authorization: `Bearer ${accessToken}`,
"X-Api-Key": adobeADCConfig.clientId,
},
});
console.log("Response Status:", response.status);
if (!response.ok) {
throw new Error(`AEM API Error: ${response.statusText}`);
}
return await response.text();
} catch (error) {
throw new Error(`Failed to fetch asset metadata: ${error.message}`);
}
}
// Helper function to filter the metadata properties like pim: and dc:
function getFilteredMetadata(data) {
if (!data || !data.assetMetadata) {
throw new Error("Invalid metadata structure received from API");
}
const properties = data.assetMetadata;
return Object.keys(properties).reduce((filtered, key) => {
if (
key.startsWith("pim:") ||
key === "dc:title" ||
key === "dc:description"
) {
filtered[key] = properties[key];
}
return filtered;
}, {});
}
// API Route: Update AEM Asset Metadata
router.post("/api/updateAEMAssetMetadata", async (req, res) => {
const { assetId, metadata } = req.body;
if (!assetId || !metadata || typeof metadata !== "object") {
return res.status(400).json({ error: "Invalid or Missing Metadata" });
}
const bucketName = getBucketName(aemAssetsConfig.hostname);
if (!bucketName) {
return res.status(400).json({ error: "Missing AEM Information" });
}
const accessToken = req.session.accessToken;
if (!accessToken) {
return res.status(401).json({ error: "Not Authenticated with Adobe IMS" });
}
try {
const updatedMetadata = await invokePatchAssetMetadataAPI(
bucketName,
assetId,
metadata,
accessToken
);
res.status(200).json(updatedMetadata);
} catch (error) {
console.error("Error updating asset metadata:", error.message);
res.status(500).json({ error: `Internal Server Error: ${error.message}` });
}
});
// Helper function to invoke the AEM API to update asset metadata
async function invokePatchAssetMetadataAPI(
bucketName,
assetId,
metadata,
accessToken
) {
const apiUrl = `https://${bucketName}.adobeaemcloud.com/adobe/assets/${assetId}/metadata`;
const headers = {
"Content-Type": "application/json-patch+json",
"If-Match": "*",
"X-Adobe-Accept-Experimental": "1",
Authorization: `Bearer ${accessToken}`,
"X-Api-Key": adobeADCConfig.clientId,
};
try {
const response = await fetch(apiUrl, {
method: "PATCH",
headers,
body: JSON.stringify(getTransformedMetadata(metadata)),
});
if (!response.ok) {
throw new Error(`AEM API Error: ${response.statusText}`);
}
return await response.json();
} catch (error) {
throw new Error(`Failed to update asset metadata: ${error.message}`);
}
}
// Helper function to transform metadata into JSON Patch format, e.g. [{ op: "add", path: "dc:title", value: "New Title" }]
function getTransformedMetadata(metadata) {
return Object.keys(metadata).map((key) => ({
op: "add",
path: `/${key}`,
value: metadata[key],
}));
}
The OpenAPI-based AEM API calls are made from the server-side (Express middleware) and not directly from the client-side (browser) to ensure that the access token is securely managed and not exposed to the client-side.
Refreshing the access token
To refresh the access token before it expires, you can implement the refresh token flow. However, to keep the tutorial simple, the WKND-PIM web app does not implement the refresh token flow.
Setup and run web app
Let’s configure and run the WKND-PIM web app on your local machine to understand the OAuth Web App authentication flow and API calls.
Prerequisites
To complete this tutorial, you need:
-
Modernized AEM as a Cloud Service environment with the following:
- AEM Release
2024.10.18459.20241031T210302Z
or later. - New style Product Profiles (if environment was created before November 2024)
- AEM Release
-
The sample WKND Sites project must be deployed on to it.
-
Access to the Adobe Developer Console.
-
Install Node.js on your local machine to run the sample NodeJS application.
-
Install a local-ssl-proxy on your local machine to create a local SSL HTTP proxy using a self-signed certificate.
Development steps
The high-level development steps are:
-
Modernization of AEM as a Cloud Service environment.
-
Enable AEM APIs access.
-
Create Adobe Developer Console (ADC) Project.
-
Configure ADC Project
- Add desired OpenAPI-based AEM APIs
- Configure OAuth Web App credential
-
Configure the AEM instance to enable ADC Project communication
-
Create and apply asset metadata schema
-
Configure and run the WKND-PIM web app
-
Verify the end-to-end flow
Modernization of AEM as a Cloud Service environment
Refer to the Modernization of AEM as a Cloud Service environment section from the How to invoke OpenAPI-based AEM APIs tutorial.
Enable AEM APIs access
Refer to the Enable AEM APIs access section from the How to invoke OpenAPI-based AEM APIs tutorial.
Create Adobe Developer Console (ADC) Project
Refer to the Create Adobe Developer Console (ADC) Project section from the How to invoke OpenAPI-based AEM APIs tutorial.
Configure ADC Project
Next, configure the ADC Project to access the Assets Author API using the OAuth Web App authentication.
-
To add AEM APIs, click on the Add API button.
-
In the Add API dialog, filter by Experience Cloud and select the AEM Assets Author API card and click Next.
-
Next, in the Configure API dialog, select the User Authentication authentication option and click Next.
-
In the next Configure API dialog, select the OAuth Web App authentication option and click Next.
-
In the Configure OAuth Web App dialog, enter the following details and click Next.
- Default redirect URI:
https://localhost:3001/callback
- Redirect URI pattern:
https://localhost:3001/callback
- Default redirect URI:
-
Review the available scopes and click Save configured API.
-
Review the AEM API and authentication configuration.
Configure the AEM instance to enable ADC Project communication
To enable the ADC Project’s OAuth Web App credential ClientID to communicate with the AEM instance, you need to configure the AEM instance.
It is done by defining the configuration in the config.yaml
file in the AEM Project. Then, deploy the config.yaml
file using the Config Pipeline in the Cloud Manager.
-
In AEM Project, locate or create the
config.yaml
file from theconfig
folder. -
Add the following configuration to the
config.yaml
file.code language-yaml kind: "API" version: "1.0" metadata: envTypes: ["dev", "stage", "prod"] data: allowedClientIDs: author: - "<ADC Project's OAuth Web App credential ClientID>"
Replace
<ADC Project's OAuth Web App credential ClientID>
with the actual ClientID of the ADC Project’s OAuth Web App credential. The API endpoint that is used in this tutorial is available only on the author tier, but for other APIs, the yaml config can also have a publish or preview node.note caution CAUTION For demo purposes, the same ClientID is used for all environments. It is recommended to use separate ClientID per environment (dev, stage, prod) for better security and control. -
Commit the config changes to the Git repository and push the changes to the remote repository.
-
Deploy the above changes using the Config Pipeline in the Cloud Manager. Note that the
config.yaml
file can also be installed in an RDE, using command line tooling.
Create and apply asset metadata schema
By default, the WKND Sites project does not have the required asset metadata schema to store product attributes. Let’s create and apply the asset metadata schema to an asset folder in the AEM instance.
-
Log in to the AEM as a Cloud Service Asset instance. Using the Asset view navigate to the
/content/dam/wknd-shared/en
folder. -
Create a PIM and within it create the Camping folder, then upload sample images in the Camping folder.
Next, let’s create the PIM attribute specific metadata schema and apply it to the PIM folder.
-
Navigate to the Settings > Metadata Forms option from the left rail and click the Create button.
-
In the Create Metadata Form dialog, enter the following details and click Create.
- Name:
PIM
- Use existing form structure as template:
Check
- Choose from:
default
- Name:
-
Click the + icon to add a new PIM tab and add Single Line Text components to it. The metadata property names should start with
pim:
prefix.table 0-row-3 1-row-3 2-row-3 3-row-3 4-row-3 5-row-3 6-row-3 Label Placeholder Metadata property SKU Enter SKU ID pim:sku
Product Type e.g., Backpack, Tent, Jacket pim:productType
Product Category e.g., Camping, Hiking, Climbing pim:productCategory
Manufacturer Enter manufacturer name pim:manufacturer
Model Enter model name pim:model
Brand Name Enter brand name pim:brandName
-
Click Save and Close to save the metadata form.
-
Finally, apply the PIM metadata schema to the PIM folder.
With the above steps, the assets from the PIM folder are ready to store the product attributes metadata.
Configure and run the WKND-PIM web app
-
Download the WKND-PIM web app zip file and extract it.
-
Navigate to the extracted folder and copy the
.env.example
file to.env
. -
Update the
.env
file with the required configuration parameters from the Adobe Developer Console (ADC) Project and AEM as a Cloud Service Assets instance.code language-plaintext ######################################################################## # Adobe IMS, Adobe Developer Console (ADC), and AEM Assets Information ######################################################################## # Adobe IMS OAuth endpoints ADOBE_IMS_AUTHORIZATION_ENDPOINT=https://ims-na1.adobelogin.com/ims/authorize/v2 ADOBE_IMS_TOKEN_ENDPOINT=https://ims-na1.adobelogin.com/ims/token/v3 ADOBE_IMS_USERINFO_ENDPOINT=https://ims-na1.adobelogin.com/ims/userinfo/v2 # Adobe Developer Console (ADC) Project OAuth Web App credential ADC_CLIENT_ID=e1adsfsd59384320bbe4f9298f00b7ab ADC_CLIENT_SECRET=p8e-Mdfgfdsg43RHugVRTEOyWlmEU5m ADC_SCOPES=AdobeID,openid,aem.folders,aem.assets.author # AEM Assets Information AEM_ASSET_HOSTNAME=https://author-p3947-e1542138.adobeaemcloud.com/ AEM_ASSET_IDS=urn:aaid:aem:aa689a9f-04da-4fbb-b460-74a5b6a69090,urn:aaid:aem:e4fdb6f6-1007-4e84-9726-a9522931786a ################################################ # Web App Information ################################################ # The port number on which this server (web app) will run PORT = 3000 # The URL to which the user will be redirected after the OAuth flow is complete REDIRECT_URI=http://localhost:3000/auth/callback # The Express (express-session) uses this secret to encrypt and verify the authenticity of that cookie # For demonstration purposes, this is a simple secret. In production, you should use a strong secret EXPRESS_SESSION_SECRET=1234554321
The
AEM_ASSET_IDS
are thejcr:uuid
property value of the uploaded images in the Camping folder. Refer to this section for more details. -
Open a terminal and navigate to the extracted folder. Install the required dependencies using the following command.
code language-bash $ npm install
-
Start the WKND-PIM web app using the following command.
code language-bash $ npm start
-
Run the local SSL HTTP proxy using the following command.
code language-bash $ local-ssl-proxy --source 3001 --target 3000 --cert ./ssl/server.crt --key ./ssl/server.key
The local SSL HTTP proxy is used as IMS requires the redirect URI to be HTTPS.
Verify the end-to-end flow
-
Open a browser and navigate to
https://localhost:3001
to access the WKND-PIM web app. Accept the self-signed certificate warning. -
Click Try Now to review and update the product attributes metadata. It initiates the IMS user authentication and app authorization flow.
-
Login with your Adobe ID credentials and provide the consent to allow the WKND-PIM web app to access the Assets Author API on your behalf.
-
From the
https://localhost:3001/update-product-attributes
route/page, click the AEM Asset Attributes tab. From the Asset ID dropdown, select an Asset ID to view the asset metadata. -
Update the asset metadata and click Update AEM Asset Attributes to update the asset metadata.
Review the application code
Let’s review the high-level code structure and main entry points of the WKND-PIM web app. The application is developed using Node.js + Express.
-
The
app.js
is the main entry point of the application. It initializes the Express app, sets up the session, and mounts the routes. -
The
public
folder contains the static assets like CSS, JavaScript, and images. Thescript.js
file contains the client-side JavaScript code to interact with the Express/api/getAEMAssetMetadata
and/api/updateAEMAssetMetadata
routes. -
The
routes
folder contains the Express routes:index.js
: The main route that renders the home page.update-product-attributes.js
: The route that renders the product attributes form with tabs, also verifies Express session for access token.adobe-ims-auth.js
: The route that initiates the Adobe IMS user authentication and app authorization flow.invoke-aem-apis.js
: The route that invokes the OpenAPI-based AEM APIs using the user-specific access token.
-
The
views
folder contains the EJS templates to render the HTML pages. -
The
utils
folder contains the utility functions. -
The
ssl
folder contains the self-signed certificate and key files to run the local SSL HTTP proxy.
You can develop or integrate the existing web app with the Adobe APIs using other server-side technologies like Java, Python, or .NET.
Summary
In this tutorial, you learned how to invoke OpenAPI-based AEM APIs on AEM as a Cloud Service from a custom web app using OAuth Web App authentication. You reviewed the key code snippets used in the WKND-PIM web app to understand the OAuth Web App authentication flow.
You can use the tutorial as a reference to integrate the OpenAPI-based AEM APIs with your custom web applications to deliver efficient, user-centric workflows.