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 SSO 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 (/product) is protected by ForgeRock® Identity Gateway (IG) and AM.

The code demonstrates how to use the pre-built IdentityGatewayAdviceInterceptor as an OkHttp Intercpetor to intercept the TransactionConditionAdvice response from IG.

It also shows how to use AdviceDialogHandler() to manipulate the UI dialog to handle Callback for transactional authorization:

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

// Use pre-built interceptor to handle response format from IG:
builder.addInterceptor(new IdentityGatewayAdviceInterceptor() {
    @Override
    public AdviceHandler getAdviceHandler(policyAdvice advice) {
        // Pre-built handler to handle Advice, for example display a dialog to trigger a tree:
        return new AdviceDialogHandler();
    }
});

// The SSO Session will be injected with the cookie header:
builder.cookieJar(SecureCookieJar.builder().build());

OkHttpClient client = builder.build();

// IG is proxying the request:
Request request = new Request.Builder().url("http://openig.example.com:8080/products").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 {
        // Handle Success 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-2022 ForgeRock, all rights reserved.