Used the Local Development Access Token

Local Development Access Token - External Application

  1. Download the temporary Local Development Access Token from AEM Developer Console
    • The Local Development Access Token expires every 24 hours, so developers need to do download new access tokens daily
  2. An External Application is being developed that programmatically interacts with AEM as a Cloud Service
  3. The External Application reads in the Local Development Access Token
  4. The External Application constructs HTTP requests to AEM as a Cloud Service, adding the Local Development Access Token as a Bearer token to the HTTP requests’ Authorization header
  5. AEM as a Cloud Service receives the HTTP request, authenticates the request, and performs the work requested by the HTTP request, and returns an HTTP response back to the External Application

The Sample External Application

We’ll create a simple external JavaScript application to illustrate how to programmatically access AEM as a Cloud Service over HTTPS using the local developer access token. This illustrates how any application or system running outside of AEM, regardless of framework or language, can use the access token to programmatically authenticate to, and access, AEM as a Cloud Service. In the next section, we’ll update this application code to support the approach for generating a token for production use.

This sample application is run from the command line, and updates AEM asset metadata using AEM Assets HTTP APIs, using the following flow:

  1. Reads in parameters from the command line (getCommandLineParams())
  2. Obtains the access token used to authenticate to AEM as a Cloud Service (getAccessToken(...))
  3. Lists all assets in a AEM asset folder specified in a command-line parameters (listAssetsByFolder(...))
  4. Update listed assets’ metadata with values specified in command-line parameters (updateMetadata(...))

The key element in programmatically authenticating to AEM using the access token is adding an Authorization HTTP request header to all HTTP requests made to AEM, in the following format:

  • Authorization: Bearer ACCESS_TOKEN

Running the External Application

  1. Ensure that Node.js is installed on your local development machine, which is used to run the external application

  2. Download and unzip the sample external application

  3. From the command line, in this project’s folder, run npm install

  4. Copy the downloaded the Local Development Access Token to a file named local_development_token.json in the root of the project

    • But remember, never commit any credentials to Git!
  5. Open index.js and review the external application code and comments.

    const fetch = require('node-fetch');
    const fs = require('fs');
    const auth = require('@adobe/jwt-auth');
    
    // The root context of the Assets HTTP API
    const ASSETS_HTTP_API = '/api/assets';
    
    // Command line parameters
    let params = { };
    
    /**
    * Application entry point function
    */
    (async () => {
        console.log('Example usage: node index.js aem=https://author-p1234-e5678.adobeaemcloud.com propertyName=metadata/dc:rights "propertyValue=WKND Limited Use" folder=/wknd-shared/en/adventures/napa-wine-tasting file=credentials-file.json' );
    
        // Parse the command line parameters
        params = getCommandLineParams();
    
        // Set the access token to be used in the HTTP requests to be local development access token
        params.accessToken = await getAccessToken(params.developerConsoleCredentials);
    
        // Get a list of all the assets in the specified assets folder
        let assets = await listAssetsByFolder(params.folder);
    
        // For each asset, update it's metadata
        await assets.forEach(asset => updateMetadata(asset, {
            [params.propertyName]: params.propertyValue
        }));
    })();
    
    /**
    * Returns a list of Assets HTTP API asset URLs that reference the assets in the specified folder.
    *
    * https://experienceleague.adobe.com/docs/experience-manager-cloud-service/assets/admin/mac-api-assets.html?lang=en#retrieve-a-folder-listing
    *
    * @param {*} folder the Assets HTTP API folder path (less the /content/dam path prefix)
    */
    async function listAssetsByFolder(folder) {
        return fetch(`${params.aem}${ASSETS_HTTP_API}${folder}.json`, {
                method: 'get',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + params.accessToken // Provide the AEM access token in the Authorization header
                },
            })
            .then(res => {
                console.log(`${res.status} - ${res.statusText} @ ${params.aem}${ASSETS_HTTP_API}${folder}.json`);
    
                // If success, return the JSON listing assets, otherwise return empty results
                return res.status === 200 ? res.json() : { entities: [] };
            })
            .then(json => {
                // Returns a list of all URIs for each non-content fragment asset in the folder
                return json.entities
                    .filter((entity) => entity['class'].indexOf('asset/asset') === -1 && !entity.properties.contentFragment)
                    .map(asset => asset.links.find(link => link.rel.find(r => r === 'self')).href);
            });
    }
    
    /**
    * Update the metadata of an asset in AEM
    *
    * https://experienceleague.adobe.com/docs/experience-manager-cloud-service/assets/admin/mac-api-assets.html?lang=en#update-asset-metadata
    *
    * @param {*} asset the Assets HTTP API asset URL to update
    * @param {*} metadata the metadata to update the asset with
    */
    async function updateMetadata(asset, metadata) {
        await fetch(`${asset}`, {
                method: 'put',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + params.accessToken // Provide the AEM access token in the Authorization header
                },
                body: JSON.stringify({
                    class: 'asset',
                    properties: metadata
                })
            })
            .then(res => {
                console.log(`${res.status} - ${res.statusText} @ ${asset}`);
            });
    }
    
    /**
    * Parse and return the command line parameters. Expected params are:
    *
    * - aem = The AEM as a Cloud Service hostname to connect to.
    *              Example: https://author-p12345-e67890.adobeaemcloud.com
    * - folder = The asset folder to update assets in. Note that the Assets HTTP API do NOT use the JCR `/content/dam` path prefix.
    *              Example: '/wknd-shared/en/adventures/napa-wine-tasting'
    * - propertyName = The asset property name to update. Note this is relative to the [dam:Asset]/jcr:content node of the asset.
    *              Example: metadata/dc:rights
    * - propertyValue = The value to update the asset property (specified by propertyName) with.
    *              Example: "WKND Free Use"
    * - file = The path to the JSON file that contains the credentials downloaded from AEM Developer Console
    *              Example: local_development_token_cm_p1234-e5678.json
    */
    function getCommandLineParams() {
        let parameters = {};
    
        // Parse the command line params, splitting on the = delimiter
        for (let i = 2; i < process.argv.length; i++) {
            let key = process.argv[i].split('=')[0];
            let value = process.argv[i].split('=')[1];
    
            parameters[key] = value;
        };
    
        // Read in the credentials from the provided JSON file
        if (parameters.file) {
            parameters.developerConsoleCredentials = JSON.parse(fs.readFileSync(parameters.file));
        }
    
        console.log(parameters);
    
        return parameters;
    }
    
    async function getAccessToken(developerConsoleCredentials) {s
        if (developerConsoleCredentials.accessToken) {
            // This is a Local Development access token
            return developerConsoleCredentials.accessToken;
        }
    }
    

    Review the fetch(..) invocations in the listAssetsByFolder(...) and updateMetadata(...), and notice headers define the Authorization HTTP request header with a value of Bearer ACCESS_TOKEN. This is how the HTTP request originating from the external application authenticates to AEM as a Cloud Service.

    ...
    return fetch(`${params.aem}${ASSETS_HTTP_API}${folder}.json`, {
                method: 'get',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + params.accessToken // Provide the AEM access token in the Authorization header
                },
    })...
    

    Any HTTP requests to AEM as a Cloud Service, must set the Bearer access token in the Authorization header. Remember, each AEM as a Cloud Service environment requires its own access token. Development’s access token does not work on Stage or Production, Stage’s does not work on Development or Production, and Production’s does not work on Development or Stage!

  6. Using the command line, from the root of the project execute the application, passing in the following parameters:

    $ node index.js \
        aem=https://author-p1234-e5678.adobeaemcloud.com \
        folder=/wknd-shared/en/adventures/napa-wine-tasting \
        propertyName=metadata/dc:rights \
        propertyValue="WKND Limited Use" \
        file=local_development_token.json
    

    The following parameters are passed in:

    • aem: The scheme and host name of the AEM as a Cloud Service environment the application interacts with (ex. https://author-p1234-e5678.adobeaemcloud.com).
    • folder: The asset folder path whose assets are updated with the propertyValue; do NOT add the /content/dam prefix (ex. /wknd-shared/en/adventures/napa-wine-tasting)
    • propertyName: The asset property name to update, relative to [dam:Asset]/jcr:content (ex. metadata/dc:rights).
    • propertyValue: The value to set the propertyName to; values with spaces need to be encapsulated with " (ex. "WKND Limited Use")
    • file: The relative file path to the JSON file downloaded from AEM Developer Console.

    A successful execution of the application results output for each asset updated:

    200 - OK @ https://author-p1234-e5678.adobeaemcloud.com/api/assets/wknd-shared/en/adventures/napa-wine-tasting.json
    200 - OK @ https://author-p1234-e5678.adobeaemcloud.com/api/assets/wknd-shared/en/adventures/napa-wine-tasting/AdobeStock_277654931.jpg.json
    200 - OK @ https://author-p1234-e5678.adobeaemcloud.com/api/assets/wknd-shared/en/adventures/napa-wine-tasting/AdobeStock_239751461.jpg.json
    200 - OK @ https://author-p1234-e5678.adobeaemcloud.com/api/assets/wknd-shared/en/adventures/napa-wine-tasting/AdobeStock_280313729.jpg.json
    200 - OK @ https://author-p1234-e5678.adobeaemcloud.com/api/assets/wknd-shared/en/adventures/napa-wine-tasting/AdobeStock_286664352.jpg.json