PingOne Advanced Identity Cloud

Get an access token in a journey

While this PingOne Advanced Identity Cloud use case was validated for accuracy, it can always be improved. To provide feedback, click thumb_up or thumb_down in the top right of this page (you must be logged into Backstage).

Description

Estimated time to complete: 25 minutes

In this use case, create a script to get an access token using a service account. A service account lets you request access tokens for most Advanced Identity Cloud REST API endpoints. Then, create a simple journey with this script to prove you can successfully request an access token.

The script in this use case uses the fr:idm:* scope for access to /openidm/* API endpoints. If you want to get an access token for other Advanced Identity Cloud API endpoints, set up your service account for the required scopes and update the scopes referenced in the script to match.

Service accounts can only be used with Advanced Identity Cloud API endpoints; if you want to communicate with a third-party API, you’ll need to use the standard OAuth 2.0 client credential flow.

Goals

After completing this use case, you will know how to do the following:

Prerequisites

Before you start work on this use case, ensure you have these prerequisites:

  • A basic understanding of:

    • Journeys and nodes

    • JavaScript

    • Service accounts

    • ESVs

  • Access to your Advanced Identity Cloud development environment as a tenant administrator.

  • A service account that can grant the fr:idm:* scope to an access token.

    You’ll need the service account ID and private key later when you create ESVs.

Tasks

Task 1: Create ESVs

  1. In the Advanced Identity Cloud admin UI, go to Tenant Settings > Global Settings > Environment Secrets & Variables.

  2. Create the following ESVs:

    ESV name ESV type Description

    esv-tenant-env-fqdn

    Variable (string)

    Tenant FQDN (in the appropriate FQDN format); for example: openam-mycompany-ew2.id.forgerock.io

    esv-service-account-id

    Secret

    Service account ID; for example: 449d7e27-7889-47af-a736-83b6bbf97ec5

    esv-service-account-privatekey

    Secret

    Service account private key; for example:

    {
      d: "RhpIZ32rNfkoVkQh3pt1me...rDkFL9nBWDOZvXQ2LsFEBc",
      dp: "RfrvtBH_NmzxS......IpJ1vYZS_J0cw",
      dq: "OVO8_yXFRHT...2VREB2b8f8xvIhv5jrQWQ",
      e: "AQAB",
      kty: "RSA",
      n: "5LoH3Fc8IdRg1...K4eUvMEJsjVvfRgzpWCDM0",
      p: "_wjzIYyYcQiNOZdV1Cp7...kjDw",
      q: "5ZeYq0C......6WOaiYw",
      qi: "Z9ECeon...q56tpl2Mu65yqlw",
    }
  3. Apply the updates.

    Learn more about creating ESVs and applying updates in Manage ESVs using the UI .

Task 2: Create a script to get an access token

  1. Download the sample script: getAccessToken.js.

    View script
    /*
     * Copyright © 2024 Ping Identity Corporation
     *
     * This code is to be used exclusively in connection with Ping Identity Corporation
     * software or services.
     * Ping Identity Corporation only offers such software or services to legal entities
     * who have entered into a binding license agreement with Ping Identity Corporation.
    */
    
    var nodeConfig = {
      nodeName: "Get Access Token Demo",
      tenantFqdnEsv: "esv.tenant.env.fqdn",
      accountIdEsv: "esv.service.account.id",
      privateKeyEsv: "esv.service.account.privatekey",
      accessTokenStateField: "idmAccessToken",
      maxAttempts: 3,
      scope: "fr:idm:*",
      serviceAccountClientId: "service-account",
      jwtValiditySeconds: 10,
    };
    
    var nodeLogger = {
      debug: function (message) {
        logger.message("***" + nodeConfig.nodeName + " " + message);
      },
      warning: function (message) {
        logger.warning("***" + nodeConfig.nodeName + " " + message);
      },
      error: function (message) {
        logger.error("***" + nodeConfig.nodeName + " " + message);
      },
    };
    
    var nodeOutcomes = {
      SUCCESS: "Success",
      ERROR: "Error",
    };
    
    var javaImports = JavaImporter(
      org.forgerock.openam.auth.node.api.Action,
      org.forgerock.json.jose.builders.JwtBuilderFactory,
      org.forgerock.json.jose.jwt.JwtClaimsSet,
      org.forgerock.json.jose.jws.JwsAlgorithm,
      org.forgerock.json.jose.jws.SignedJwt,
      org.forgerock.json.jose.jws.handlers.SecretRSASigningHandler,
      org.forgerock.json.jose.jwk.RsaJWK,
      javax.crypto.spec.SecretKeySpec,
      org.forgerock.secrets.SecretBuilder,
      org.forgerock.secrets.keys.SigningKey,
      java.time.temporal.ChronoUnit,
      java.time.Clock,
      java.util.UUID
    );
    
    function getKeyFromJwk(issuer, jwk) {
      var privateKey = javaImports.RsaJWK.parse(jwk).toRSAPrivateKey();
    
      var secretBuilder = new javaImports.SecretBuilder();
    
      secretBuilder
        .secretKey(privateKey)
        .stableId(issuer)
        .expiresIn(
          5,
          javaImports.ChronoUnit.MINUTES,
          javaImports.Clock.systemUTC()
        );
      return new javaImports.SigningKey(secretBuilder);
    }
    
    function getAssertionJwt(accountId, privateKey, audience, validity) {
      var signingHandler = new javaImports.SecretRSASigningHandler(
        getKeyFromJwk(accountId, privateKey)
      );
    
      var iat = new Date().getTime();
      var exp = new Date(iat + validity * 1000);
    
      var jwtClaims = new javaImports.JwtClaimsSet();
    
      jwtClaims.setIssuer(accountId);
      jwtClaims.setSubject(accountId);
      jwtClaims.addAudience(audience);
      jwtClaims.setExpirationTime(exp);
      jwtClaims.setJwtId(javaImports.UUID.randomUUID());
    
      var jwt = new javaImports.JwtBuilderFactory()
        .jws(signingHandler)
        .headers()
        .alg(javaImports.JwsAlgorithm.RS256)
        .done()
        .claims(jwtClaims)
        .build();
    
      return jwt;
    }
    
    function getAccessToken(accountId, privateKey, tenantFqdn, maxAttempts) {
      var response = null;
      var accessToken = null;
      var tokenEndpoint = "https://"
        .concat(tenantFqdn)
        .concat("/am/oauth2/access_token");
    
      nodeLogger.debug("Getting Access Token from endpoint " + tokenEndpoint);
    
      var assertionJwt = getAssertionJwt(
        accountId,
        privateKey,
        tokenEndpoint,
        nodeConfig.jwtValiditySeconds
      );
    
      if (!assertionJwt) {
        nodeLogger.error("Error getting assertion JWT");
        return null;
      }
    
      nodeLogger.debug("Got assertion JWT " + assertionJwt);
    
      for (var attempt = 0; attempt < maxAttempts; attempt++) {
        nodeLogger.debug("Attempt " + (attempt + 1) + " of " + maxAttempts);
        try {
          var request = new org.forgerock.http.protocol.Request();
          request.setUri(tokenEndpoint);
          request.setMethod("POST");
          request
            .getHeaders()
            .add("Content-Type", "application/x-www-form-urlencoded");
    
          var params = "grant_type="
            .concat(
              encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer")
            )
            .concat("&client_id=")
            .concat(encodeURIComponent(nodeConfig.serviceAccountClientId))
            .concat("&assertion=")
            .concat(encodeURIComponent(assertionJwt))
            .concat("&scope=")
            .concat(encodeURIComponent(nodeConfig.scope));
    
          request.setEntity(params);
          response = httpClient.send(request).get();
          if (response) {
            break;
          }
        } catch (e) {
          nodeLogger.error(
            "Failure calling access token endpoint: " +
              tokenEndpoint +
              " exception:" +
              e
          );
        }
      }
    
      if (!response) {
        nodeLogger.error("Bad response");
        return null;
      }
    
      if (response.getStatus().getCode() !== 200) {
        nodeLogger.error(
          "Unable to acquire Access Token. HTTP Result: " + response.getStatus()
        );
        return null;
      }
    
      try {
        var responseJson = response.getEntity().getString();
        nodeLogger.debug("Response content " + responseJson);
        var oauth2response = JSON.parse(responseJson);
        accessToken = oauth2response.access_token;
        nodeLogger.debug("Access Token acquired: " + accessToken);
        return accessToken;
      } catch (e) {
        nodeLogger.error("Error getting access token from response: " + e);
      }
    
      return null;
    }
    
    (function () {
      try {
        nodeLogger.debug("Node starting");
    
        var accessToken = nodeState.get(nodeConfig.accessTokenStateField);
    
        if (accessToken) {
          nodeLogger.debug("Access token already present: continuing");
          action = javaImports.Action.goTo(nodeOutcomes.SUCCESS).build();
          return;
        }
    
        var tenantFqdn = systemEnv.getProperty(nodeConfig.tenantFqdnEsv);
        if (!tenantFqdn) {
          nodeLogger.error("Couldn't get FQDN from esv " + config.tenantFqdnEsv);
          action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
          return;
        }
    
        var accountId = systemEnv.getProperty(nodeConfig.accountIdEsv);
        if (!accountId) {
          nodeLogger.error(
            "Couldn't get service account id from esv " + nodeConfig.accountIdEsv
          );
          action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
          return;
        }
    
        var privateKey = systemEnv.getProperty(nodeConfig.privateKeyEsv);
        if (!privateKey) {
          nodeLogger.error(
            "Couldn't get private key from esv " + nodeConfig.privateKey
          );
          action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
          return;
        }
    
        accessToken = getAccessToken(
          accountId,
          privateKey,
          tenantFqdn,
          nodeConfig.maxAttempts
        );
    
        if (!accessToken) {
          nodeLogger.error("Failed to get access token");
          action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
          return;
        }
    
        nodeLogger.debug("Success - adding token to transient state");
        nodeState.putTransient(nodeConfig.accessTokenStateField, accessToken);
        action = javaImports.Action.goTo(nodeOutcomes.SUCCESS).build();
      } catch (e) {
        nodeLogger.error("Exception encountered " + e);
        action = javaImports.Action.goTo(nodeOutcomes.ERROR).build();
        return;
      }
    })();
  2. In the Advanced Identity Cloud admin UI, go to code Scripts > Auth Scripts and click + New Script.

  3. Select Journey Decision Node and click Next.

  4. Select Legacy and click Next.

  5. In the New Journey Decision Node Script window, enter the following details:

    Field Value

    Name

    Get access token

    Description

    Get an access token using a service account

    JavaScript

    Replace the sample JavaScript with the contents of the downloaded getAccessToken.js script.

  6. Check the variables defined in the script and update as needed:

    var nodeConfig = {
      nodeName: "Get Access Token Demo", (1)
      tenantFqdnEsv: "esv.tenant.env.fqdn", (2)
      accountIdEsv: "esv.service.account.id", (3)
      privateKeyEsv: "esv.service.account.privatekey", (4)
      accessTokenStateField: "idmAccessToken",
      maxAttempts: 3,
      scope: "fr:idm:*", (5)
      serviceAccountClientId: "service-account", (6)
      jwtValiditySeconds: 10,
    };
    1 The nodeName indicates the name of your journey for debugging purposes.
    2 The tenantFqdnEsv contains the script reference to the esv-tenant-env-fqdn ESV.
    3 The accountIdEsv contains the script reference to the esv-service-account-id ESV.
    4 The privateKeyEsv contains the script reference to the esv-service-account-privatekey ESV.
    5 The scope you chose when you set up your service account; this determines which API endpoints you can get an access token for.
    6 The serviceAccountClientId must be set to service-account to use the built-in OAuth 2.0 public client for service accounts; otherwise, the JWT profile for OAuth 2.0 authorization grant flow will fail.
  7. Click Save and Close.

Task 3: Create a journey to get an access token

  1. In the Advanced Identity Cloud admin UI, go to account_tree Journeys and click + New Journey.

  2. In the New Journey window, enter the following details:

    Field Value

    Name

    Get Access Token Demo

    Identity Object

    Alpha realm - Users managed/alpha_user

    Description

    Journey to get an access token using a service account

  3. Click Save. The journey editor displays.

  4. In the journey editor, search for the Scripted Decision node and drag it onto the canvas.

  5. Configure this node as follows:

    Field Value

    Name

    Get Access Token

    Script

    Get access token

    Outcomes

    Create two outcomes: Success and Error

  6. In the journey editor, search for the Message Node and drag two copies of it onto the canvas.

  7. Select the first Message Node and configure it as follows:

    Field Value

    Name

    Success Message

    Message

    Key

    en

    Value

    Access token successfully acquired

  8. Select the second Message Node and configure it as follows:

    Field Value

    Name

    Error Message

    Message

    Key

    en

    Value

    Failed to get access token

  9. Connect the nodes:

    Get access token journey
    Source node Outcome path Target node

    Start (person icon)

    Scripted Decision node

    (Get Access Token)

    Scripted Decision node

    (Get Access Token)

    Success

    Message node

    (Success Message)

    Error

    Message node

    (Error Message)

    Message node

    (Success Message)

    True

    Success node

    False

    Success node

    Message node

    (Error Message)

    True

    Failure node

    False

    Failure node

  10. Click Save.

Check in

At this point, you:

Created ESVs for the tenant FQDN and service account details.

Created a script to get an access token using a service account.

Created a simple journey to get an access token.

Validation

Now that you have created a journey to get an access token, you are ready to validate it.

The journey runs the script to acquire an access token using the service account and ESVs you set up. If an access token is successfully acquired, the Success Message is shown.

If you want to view the access token created during testing, you can enable debug mode in your development environment.

Steps

  1. In the Advanced Identity Cloud admin UI, go to account_tree Journeys and click on the Get Access Token Demo journey you just created.

  2. In the Preview URL field, click copy and paste the URL into an Incognito window.

    The script runs to get an access token, and if successful, the Success Message displays:

    Success message

    The Yes and No buttons shown are the default outcomes for a Message node; they are not relevant to this example and don’t do anything further.

    If an access token is not acquired, the Error Message is shown instead (Failed to get access token).

Explore further

Reference material

Reference Description

Service accounts

Information about service accounts in Advanced Identity Cloud.

Authenticate to Advanced Identity Cloud REST API with access token

Learn how to authenticate to Advanced Identity Cloud REST API endpoints using an access token.

ESVs

Information about environment secrets and variables (ESVs).

Journeys

Conceptual information on journeys and their purpose in Advanced Identity Cloud.

Nodes for journeys

Learn about the configurable nodes Advanced Identity Cloud offers for use in journeys.

Scripted decision node API

Reference information for journey decision node scripts.

Nodes used

The following nodes are listed in the order they appear in the journey.