ForgeRock SDKs

Perform transactional authorization

The ForgeRock SDKs have builtin support for transactional authorization.

Transactional authorization improves security by requiring the user to perform additional actions when trying to access a resource protected by a policy in ForgeRock® Access Management (AM). For example, they must re-authenticate to a strong authentication tree, or respond to a push notification.

How does transactional authorization work?

The following diagram shows the flow used during transactional authorization:

transactional-how-it-works

When the ForgeRock SDKs attempt to access a resource protected with transactional authorization, AM returns JSON that has an empty actions attribute. A unique transaction ID (TxId) is also included under /advices/TransactionConditionAdvice.

For example:

{
    "resource": "https://app-backend.example.com:8000/protected/feature/",
    "actions": {},
    "attributes": {},
    "advices": {
        "TransactionConditionAdvice": [
            "7b8bfd4c-60fe-4271-928d-d09b94496f84"
        ]
    },
    "ttl": 0
}

The ForgeRock SDKs detect that transactional authorization is required, and make a call to the /authenticate endpoint to begin to fulfil the requirements specified in the policy protecting the resource. The call must include the TxId originally received from AM.

AM responds to the request with a series of required callbacks to fulfil the policy.

Each callback is handled by the SDK; for example, by rendering UI for the user to complete, or responding to a push notification.

When all the callbacks have been completed, the SDK attempts to access the protected resource again, using the same session or OAuth 2.0 token as before. The SDK adds the transaction ID into the policy evaluation as an environment property:

{
    "resources" : ["https://app-backend.example.com:8000/protected/feature/"],
    "application" : "iPlanetAMWebAgentService",
    "subject" : {
        "ssoToken" : "AQIC5w....*AJTMQAA*"
    },
    "environment": {
        "TxId": ["77b8bfd4c-60fe-4271-928d-d09b94496f84"]
    }
}

As the transaction ID matches an entry in AM’s completed transaction list, AM returns a new policy evaluation result, including the actions the SDK-based application can now perform:

{
    "resource": "https://app-backend.example.com:8000/protected/feature/",
    "actions": {
        "POST": true,
        "GET": true
    },
    "attributes": {},
    "advices": {},
    "ttl": 0
}

For more information on transactional authorization, and how to set up AM to use it, see Transactional authorization in the AM documentation.

In the ForgeRock Android SDK

In this example, the protected resource (http://openig.example.com/myResource) is protected by ForgeRock® Identity Gateway (IG) and AM.

The response from IG contains the advice in the redirect URI.

The following example shows how to extract the advice using OKHttpClient:

// Since IG responds with Advice and a redirect, ensure we do not follow them:
OkHttpClient.Builder builder = new OkHttpClient.Builder()
    .followRedirects(false);

// The session token will be injected with the cookie header:
builder.cookieJar(SecureCookieJar.builder()
    .context(this.getApplicationContext())
    .build());
OkHttpClient client = builder.build();

Request request = new Request.Builder()
    .url("http://openig.example.com/myResource")
    .build();

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) {
        // Handle failure scenario...
    }

    @Override
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
        if (response.code() == StatusLine.HTTP_TEMP_REDIRECT) {

            // Capture the redirect URI
            String location = response.header("location");
            Uri redirect = Uri.parse(location);

            // Extract the advice from the captured URI
            String advice = redirect.getQueryParameter("authIndexValue");
            PolicyAdvice policyAdvice = PolicyAdvice.parse(advice);

            // Perform the transactional authorization
            FRSession.getCurrentSession().authenticate(MainActivity.this, policyAdvice, new NodeListener<FRSession>() {
                @Override
                public void onCallbackReceived(Node node) {
                    // Fulfil authentication requirements specified in the advice
                }
                @Override
                public void onSuccess(FRSession result) {
                    // Handle success scenario...
                }

                @Override
                public void onException(Exception e) {
                    // Handle failure scenario...
                }
            });
        }
    }
});

In the ForgeRock iOS SDK

The following steps demonstrate how to handle transactional authorization in the ForgeRock iOS SDK.

This example assumes interaction directly with AM. If the resource server is protected by IG, and routes are configured for protected resources, the optional steps are not required, as the SDK is able to deal directly with the responses from IG.

  1. Create an AuthorizationPolicy with an array of one or more URLs to evaluate policies against, and a delegate of AuthorizationPolicyDelegate:

    let authPolicy = AuthorizationPolicy(validatingURL: [<URL>, <URL>...], delegate: self)
  2. Add AuthorizationPolicy to FRURLProtocol

    FRURLProtocol.authorizationPolicy = authPolicy
  3. Register the FRURLProtocol class:

    URLProtocol.registerClass(FRURLProtocol.self)
  4. Create a URLSessionConfiguration with FRURLProtocol, and create a URLSession with the configuration:

    // Configure FRURLProtocol for HTTP client
    let config = URLSessionConfiguration.default
    config.protocolClasses = [FRURLProtocol.self]
    self.urlSession = URLSession(configuration: config)
  5. (Optional) If the SDK fails to parse the response into policyAdvice, construct it with the given response by implementing the AuthorizationPolicyDelegate.evaluateAuthorizationPolicy() method:

    extension YourClass: AuthorizationPolicyDelegate {
        func evaluateAuthorizationPolicy(responseData: Data?, response: URLResponse?, error: Error?) -> PolicyAdvice? {
            // With given response, construct PolicyAdvice with following methods:
            // PolicyAdvice(redirectUrl:) - create PolicyAdvice with given redirectUrl
            // PolicyAdvice(json:) - create PolicyAdvice with given JSON object
            // PolicyAdvice(type:value:) - create PolicyAdvice with given authZ type, and value
            if let policyAdvice = PolicyAdvice() {
                // If PolicyAdvice is constructed, return 'policyAdvice' to continue authZ
                return policyAdvice
            }
            else {
                // If PolicyAdvice cannot be constructed, return 'nil' to stop authZ
                return nil
            }
        }
    }
  6. Initiate authentication tree flow, including policyAdvice, by implementing the AuthorizationPolicyDelegate.onPolicyAdviseReceived() method:

     extension YourClass: AuthorizationPolicyDelegate {
      func onPolicyAdviseReceived(policyAdvice: PolicyAdvice, completion: @escaping FRCompletionResultCallback) {
          FRSession.authenticate(policyAdvice: policyAdvice) { (token: Token?, node, error) in
              if error != nil {
                  //Authentication failed
                  completion(false)
                  return
              }
              if let _ = token {
                  completion(true)
              }
              else {
                  //handle node. At the end of the authentication, you should get back a Token. In this case you will need to call the completion handler
              }
          }
      }
    }
  7. (Optional) Decorate the original URLRequest object with updated information, by implementing the AuthorizationPolicyDelegate.updateRequest() method.

    If a delegation method is not defined, the SDK appends the _txid query parameter automatically to the URL:

    extension YourClass: AuthorizationPolicyDelegate {
        func updateRequest(originalRequest: URLRequest, txId: String?) -> URLRequest {
            // append txId into the request
            return request
        }
    }

In the ForgeRock JavaScript SDK

Transactional authorization is built into the HttpClient module of the ForgeRock JavaScript SDK.

This module detects when a transactional authorization is needed, and initiates interaction with AM.

When the callbacks are returned from AM, your client app must provide the necessary user interaction.

This callback handling iterates until a success or failure is reached. On success, the SDK can re-request the initial resource endpoint.

The following code shows an example implementation:

console.log('Make a $200 withdrawal from account');
return forgerock.HttpClient.request({
    init: {
        method: 'POST',
        body: JSON.stringify({ amount: '200' }),
    },
    authorization: {
        handleStep: async (step) => {
            console.log('Withdraw action requires additional authorization');
            step.getCallbackOfType('ValidatedCreateUsernameCallback').setName(un);
            step.getCallbackOfType('ValidatedCreatePasswordCallback').setPassword(pw);
            return Promise.resolve(step);
        },
    },
    timeout: 0,
    url: `${resourceUrl}/withdraw`,
});
Copyright © 2010-2023 ForgeRock, all rights reserved.