AEMaaCS: Generating Access Tokens with Java Code and Service Accounts


Service accounts and access tokens are crucial components when accessing restricted resources in AEM as a Cloud Service, especially in scenarios where external applications interact with AEM.

What are Service Credentials and Access Token?

Service Credentials” in AEM as a Cloud Service provide secure authentication for external systems or integrations to access AEM’s APIs and services, enabling controlled and fine-grained access control while protecting sensitive data and maintaining accountability.

Access tokens“, generated through the AEM as a Cloud Service Developer Console, serve as temporary keys that grant permission to access specific AEM resources. These tokens are essential for authentication and authorization, ensuring that only authorized actions are performed, and providing a secure and auditable way for external applications to interact with AEM.

1. Generate Service Credentials

Service Credentials generation is broken into two steps:

  1. A one-time Technical Account creation by an Adobe IMS Org administrator
  2. Download Technical Account’s Service Credentials JSON

Service Credentials require a Technical Account to be created by an Adobe Org IMS Administrator before they can be downloaded.

For details on generating and downloading credentials, refer to “Fetch the AEM as a Cloud Service Credentials

2.   Configure access in AEM

In order to grant access to the service user established in the previous step, it is imperative to link them with a group holding the requisite permissions.

  1. First, locate the technical account’s AEM login name by opening the Service Credentials JSON downloaded from AEM Developer Console, and locate the integration.email value, which should look similar to: 12345678-abcd-9000-efgh-0987654321c@techacct.adobe.com.
  2. Log in to the corresponding AEM environment’s Author service as an AEM Administrator
  3. Navigate to Tools > Security > Users
  4. Locate the AEM user with the Login Name identified in Step 1, and open its Properties
    • If, for any reason, the technical account user is not yet present in AEM, you can create the account by sending an HTTP request with a valid token to AEM. This can be accomplished through tools like the curl command or Postman. You might not be able to access any resource, as group association is pending. But, a user corresponding to service account will be created.
    • Repository contains node js app (AEM-CS API Client Library) that can be used to exchange AEM-CS API Integration JSON for Access Tokens with IMS.
  5. Navigate to the Groups tab, and add the user to relevant group
  6. Tap Save and Close

3. Generate token via java code

You can readily add the following code to a maven project. Update the values for following variables in code. These values are available in the json downloaded after creating Sevice account.

  • clientId
  • clientSecret
  • org
  • id
  • metascopes

import io.jsonwebtoken.Jwts;
import org.json.JSONObject;

import javax.net.ssl.*;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

import static io.jsonwebtoken.SignatureAlgorithm.RS256;
import static java.lang.Boolean.TRUE;

public class GenerateToken {

    static String apiKey = "<clientId>";
    static String secret = "<clientSecret>";
    static String orgId = "<org>";
    static String technicalAccountId = "<id>";
    static String imsHost = "ims-na1.adobelogin.com";
    static String metascopes[] = {"< metascopes1>", "< metascopes2>"};

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, KeyManagementException {
        String jwtToken = getJWTToken();
        System.out.println("jwtToken: " + jwtToken);
        System.out.println("Access token: " + getAccessToken(jwtToken));
    }

    private static String getAccessToken(String jwtToken)
            throws IOException, NoSuchAlgorithmException, KeyManagementException {

        /* Start of the fix for SSL certs */
//        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
//            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
//                return null;
//            }
//
//            public void checkClientTrusted(X509Certificate[] certs, String authType) {
//            }
//
//            public void checkServerTrusted(X509Certificate[] certs, String authType) {
//            }
//        }};

//        SSLContext sc = SSLContext.getInstance("SSL");
//        sc.init(null, trustAllCerts, new java.security.SecureRandom());
//        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        // Create all-trusting host name verifier
//        HostnameVerifier allHostsValid = new HostnameVerifier() {
//            public boolean verify(String hostname, SSLSession session) {
//                return true;
//            }
//        };

        // Install the all-trusting host verifier
//      HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
        /* End of the fix for SSL certs*/

        // Create URL and open connection
        URL url = new URL("https://ims-na1.adobelogin.com/ims/exchange/jwt");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);

        // Set headers
        connection.setRequestProperty("cache-control", "no-cache");
        connection.setRequestProperty("content-type", "application/x-www-form-urlencoded");

        // Set body
        String body = "client_id=" + URLEncoder.encode(apiKey, "UTF-8") + "&" + "client_secret="
                + URLEncoder.encode(secret, "UTF-8") + "&" + "jwt_token=" + URLEncoder.encode(jwtToken, "UTF-8");

        // Write body to output stream
        DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
        outputStream.writeBytes(body);
        outputStream.flush();
        outputStream.close();

        // Get response
        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();

        JSONObject json = new JSONObject(response.toString());

        return json.getString("access_token");
    }

    private static String getJWTToken()
            throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, KeyManagementException {

        // Expiration time in seconds
        Long expirationTime = System.currentTimeMillis() / 1000 + 86400L;

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] encodedBytes = GenerateToken.class.getClassLoader().getResourceAsStream("privateKey.pem").readAllBytes();
        KeySpec ks = new PKCS8EncodedKeySpec(encodedBytes);
        RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(ks);

        // Create JWT payload
        Map<String, Object> jwtClaims = new HashMap<>();
        jwtClaims.put("iss", orgId);
        jwtClaims.put("sub", technicalAccountId);
        jwtClaims.put("exp", expirationTime);
        jwtClaims.put("aud", "https://" + imsHost + "/c/" + apiKey);
        for (String metascope : metascopes) {
            jwtClaims.put("https://" + imsHost + "/s/" + metascope, TRUE);
        }

        return Jwts.builder().setClaims(jwtClaims).signWith(RS256, privateKey).compact();
    }
}

Create PKCS8 encoded private key.

This can be achieved by:

  • Copy the privateKey from the Service Account json to a file (like private.key). Place the file under resources folder of the maven project
  • Replace all “/r/n” with Enter. The file look like this:
  • Execute following command in same directory as the private.key file. A privateKey.pem would be created
openssl pkcs8 -topk8 -inform PEM -outform DER -in private.key -out privateKey.pem -nocrypt

Generate Access Token

  • Execute the main method. It should generate JWT token and Access token.

Resolving javax.net.ssl.SSLHandshakeException

In case you get any certificate related issues “javax.net.ssl.SSLHandshakeException”, uncomment the code between following comments:

  • /* Start of the fix for SSL certs */
  • /* End of the fix for SSL certs*/

At this juncture, you should have the capability to access AEM resources using the token via either curl or Postman.

Sample request for accessing a restricted resource via Access Token

In the case of Postman, here’s an example request. You’ll need to modify the Authorization tab as follows:

  • Type: Bearer Token
  • Insert the token generated from your Java code into the text field adjacent to ‘Token.’

6 thoughts on “AEMaaCS: Generating Access Tokens with Java Code and Service Accounts

    1. At the time of writing the blog, OAuth was available in Adminconsole. But, was not supported on AEM. We had confirmed via Adobe ticket.
      You might have to check, if OAuth available for AEM as well now

      Like

  1. Hello
    Many thanks for your quick response. Ok I understand. The OAuth server-to-server seems to be available for the other Adobe API’s but not for AEM.

    Thanks again
    Regards

    Like

Leave a comment