Customizing Single Logout Using Journeys, Pt. 3: Invalidating the

Author:

David Gwizdala

Created at:

Mar 2024

Updated at:

Mar 2024

This is part 3 of 4 in the series Customizing Single Logout Using Journeys.

Invalidating the User’s ForgeRock Session

Once you’ve captured the user’s SSO token, and have performed any necessary logout activities with external services, you can use that token to force session termination for that user. In this example, we’ll be terminating all of a user’s active sessions - but you can always view and terminate specific sessions instead. To accomplish this, we’ll be using the Session Management API within a Scripted Decision Node.

Create a new Journey entitled Terminate ForgeRock Session. In your Journey, connect your Start Node to an Inner Tree Node and select the name of your Journey you created in Capturing the User’s Existing Browser Session. If you haven’t done this step yet, jump back there first - it’ll show you how to reference the user’s cookie (which you’ll use as an SSO token to log them out).

Now that we’ve captured the session, we can optionally call the logout actions we made in Terminating an External Session via REST with another Inner Tree Node. Note that if you don’t need to call any external endpoints, you’ll need to add an Identify Existing User Node with the Identifier and Identity Attribute both set to “userName” (as mentioned in Ensuring the User Has an Active Session) as we’ll be referencing the user’s _id later.

Note: if you’d prefer not use inner trees, you can add the Scripted Decision Nodes you created in previous sections directly into this Journey instead or combine the scripts together (code example here)

Your journey will either look like this:

https://backstage-community-prod.storage.googleapis.com/original/2X/c/cb18d3f038bc88fb23dd14bc1463ad4a9bc4ba42
Logging Out the User in ForgeRock - No External Sessions

Or if you added the REST call Journey, like this:

https://backstage-community-prod.storage.googleapis.com/original/2X/0/0e417addd295c9992efa357aef2e0a7e9ea22eb1
Logging Out the User in ForgeRock - External Sessions

Sending the Request

Next, drag and drop in a Scripted Decision Node and create a Script named Terminate Sessions. This script will have four outcomes defined: Success, Failure, No Session, and Error.

Copy/Paste the following Javascript into your Node:

/*
Checks if the user has an existing session, and if so, logs them out.

 This script expects the user's cookie and _id to be stored in shared state.

 This script expects the following ESV to be set:
 - esv.cookie - the cookie of the tenant (found in "Tenant Settings")

 The scripted decision node needs the following outcomes defined:
 - Success
 - Failure
 - No Session
 - Error

 Author: se@forgerock.com
 */

// Request Params
var HOST = requestHeaders.get("host").get(0);
var AM_BASE_URL = "https://" + HOST + "/am/";
// Long-Lived API Token
var TENANT_COOKIE = systemEnv.getProperty("esv.cookie");

var OUTCOMES = {
  SUCCESS: "Success",
  FAILURE: "Failure",
  NO_SESSION: "No Session",
  ERROR: "Error"
};

//// HELPERS

/**
 * Kills all of a user's sessions based on their identifier
 * https://backstage.forgerock.com/docs/idcloud/latest/am-sessions/managing-sessions-REST.html#invalidate-sessions-user
 * @param {string} sessionCookie the user's cookie containing their session authorization
 * @param {string} uid the universal identifier of the user
 * @returns {boolean} whether or not the invalidation was successful
 */
function logoutByUser(sessionCookie, uid) {
  var wasLogoutSuccessful = false;

  var options = {
    method: 'POST',
    headers: {
      "Content-Type": "application/json; charset=UTF-8",
      "Accept-API-Version": "resource=5.1, protocol=1.0"
    },
    body: {
      username: uid
    }
  };
  options.headers[TENANT_COOKIE] = sessionCookie;

  var requestURL = `${AM_BASE_URL}json/realms/root/realms/alpha/sessions/?_action=logoutByUser`;

  var response = httpClient.send(requestURL, options).get();

  if (response.status === 200) {
    var payload = response.text();
    var jsonResult = JSON.parse(payload);
    wasLogoutSuccessful = jsonResult.result;
  }

  return wasLogoutSuccessful;
}

//// MAIN
(function () {
  try {
    var sessionCookie = nodeState.get("sessionCookie");
    var uid = nodeState.get("_id");

    if (!sessionCookie || !uid) {
      outcome = OUTCOMES.NO_SESSION;
    } else if (logoutByUser(sessionCookie, uid)) {
      outcome = OUTCOMES.SUCCESS;
    } else {
      outcome = OUTCOMES.FAILURE;
    }
  } catch(e) {
   logger.error(e);
   outcome = OUTCOMES.ERROR;
  }
}());

How it Works

This script is really doing only one action - it’s calling logoutByUser from the Session Management API using the user’s personal sessionCookie and their _id we captured earlier. Once the user’s been logged out, it returns success. That’s it!

Testing

To test that our Journey is successful, we’ll log the user out and then attempt to enter into their user portal. We’ll also use our admin portal to check for any active sessions.

To set this up, at the end of your Journey put an Inner Tree Node that points to your default Login Journey and connect to the False response of your Message Node. The True outcome of the Login Journey should point to the Success Node and the False outcome should point to the Failure node. That way when the user is invalidated they are placed back on a page to come back in if they need to.

Finally, we’ll want to make sure that any errors from our Session Invalidation Script are caught, so put a Message Node on the Error output of your Scripted Decision Node that informs you if an error occurred. In a production system, you’ll likely want to handle this error by returning to another Journey, page, or failure outcome but in this case we’ll display the message “An Error Occurred. Please view the logs.” Wire the output of your Message Node to the Failure Node.

Your completed Journey should look something like this:

https://backstage-community-prod.storage.googleapis.com/original/2X/f/fb175c2ddd4c3b5241b69246b17ad7c1b1e6e6f9
Invalidating ForgeRock Sessions Journey

Open an incognito (or separate browser) window and log in as your test user. You should see their active session within your AM Native Console under the “Sessions” tab by searching for their _id (this attribute is in your managed users page under “Raw JSON”).

https://backstage-community-prod.storage.googleapis.com/original/2X/a/a2d8073c8d1c44c97127847e1ff2543a90d4790b

https://backstage-community-prod.storage.googleapis.com/original/2X/e/e3b59e6ad01b54585a0adcf7e865341163f0256d
The User and Their Active Session

Once you’ve validated that the user’s session exists and is active, paste in the URL of your new Journey in the browser where you’re logged in as that user. You should be taken to your default login screen. When you check the AM Native console, all sessions related to that user should be gone!

https://backstage-community-prod.storage.googleapis.com/original/2X/5/51a08a58f93e0ad1bf25f89604a9e53022263d29

https://backstage-community-prod.storage.googleapis.com/original/2X/a/ab75634444527dbbae18f406069e0e16a54d0cb7
Login Screen for User and No Sessions for Admin

Summary

With this How-To you have invalidated a user’s sessions both from external applications and all sessions within ForgeRock.

The full Journey, including the testing output, can be downloaded here:

This is part of a 4-part series on Creating Custom Single Logout Using Journeys. To continue this series, head to Part 4: Redirecting the User to a Custom URL to learn how to specify where the user goes when they logout.