Set up transactional authorization
The Ping SDKs have builtin support for transactional authorization.
Transactional authorization requires a user to authorize every access to a resource. It is part of an PingAM policy that grants single-use or one-shot access.
For example, a user might approve a financial transaction with a one-time password (OTP) sent to their device, or respond to a push notification to confirm that they have indeed signed on from an unexpected location.
Performing the additional action successfully grants access to the protected resource but only once. Additional attempts to access the resource require the user to perform the configured actions again.
How does transactional authorization work?
The following diagram shows the flow used during transactional authorization:
When the Ping SDKs attempt to access a resource protected with transactional authorization,
PingAM 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 Ping 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
value originally received from PingAM.
PingAM 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 PingAM’s completed transaction list, PingAM 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 PingAM to use it, see Transactional authorization in the PingAM documentation.
Handle transactions in an Android app
In this example, an API is protected by PingGateway and PingAM.
The example adds a x-authenticate-response
header to the access request. This header causes IG to return the advice as JSON in a header named Www-Authenticate
.
The SDK provides interceptors for handling the returned advice:
After obtaining the advice, pass it to the authenticate()
method of the FRSession
class.
The following example shows how to use the interceptors to handle the advice using OKHttpClient
:
val builder: OkHttpClient.Builder = OkHttpClient.Builder()
builder.addInterceptor(object: IdentityGatewayAdviceInterceptor() {
override fun getAdviceHandler(advice: PolicyAdvice): AdviceHandler {
return object: AdviceHandler {
override suspend fun onAdviceReceived(
context: Context,
advice: PolicyAdvice) {
// Authenticate the advice with
// FRSession.getCurrentSession().authenticate(context, advice, ...)
}
}
}
})
builder.cookieJar(SecureCookieJar.builder().context(context).build())
val client: OkHttpClient = builder.build()
val requestBuilder: Request.Builder = Request.Builder().url(api)
requestBuilder.addHeader("x-authenticate-response", "header");
val request = requestBuilder.build()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
// Handle Failure
}
override fun onResponse(call: Call, response: Response) {
// Handle Response
}
})
Handle transactions in an iOS app
The following steps demonstrate how to handle transactional authorization in the Ping SDK for iOS.
This example assumes interaction directly with PingAM.
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.
-
Create an
AuthorizationPolicy
with the URL to evaluate policies against, and a delegate ofAuthorizationPolicyDelegate
:let authPolicy = AuthorizationPolicy( validatingURL: ["https://protectedendpoint"], delegate: self )
-
Add
AuthorizationPolicy
toFRURLProtocol
FRURLProtocol.authorizationPolicy = authPolicy
-
Register the
FRURLProtocol
class:URLProtocol.registerClass(FRURLProtocol.self)
-
Create a
URLSessionConfiguration
withFRURLProtocol
, and create aURLSession
with the configuration.(Optional) If using IG, add the
x-authenticate-response
header so that IG returns the advice response as JSON in a header namedWww-Authenticate
, rather than as a redirect with query parameters:// Configure FRURLProtocol for HTTP client let config = URLSessionConfiguration.default config.protocolClasses = [FRURLProtocol.self] var request = URLRequest(url: "https://protectedendpoint") request.setValue("header", forHTTPHeaderField: "x-authenticate-response") self.urlSession = URLSession(configuration: config) self.urlSession.dataTask(with: request) { (data, response, error) in }.resume()
-
(Optional) If the SDK is unable to parse the response into
policyAdvice
, construct it with the given response by implementing theAuthorizationPolicyDelegate.evaluateAuthorizationPolicy()
method:extension YourClass: AuthorizationPolicyDelegate { func evaluateAuthorizationPolicy( responseData: Data?, response: URLResponse?, error: Error? ) -> PolicyAdvice? { if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 401, let json = httpResponse.allHeaderFields["Www-Authenticate"] as? String, let policyAdvice = PolicyAdviceCreator().parseAsBase64(advice: json) { return policyAdvice } else { // If PolicyAdvice cannot be constructed, return 'nil' to stop authZ return nil } } }
-
Initiate authentication tree flow, including
policyAdvice
, by implementing theAuthorizationPolicyDelegate.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 } } } }
-
(Optional) Decorate the original
URLRequest
object with updated information, by implementing theAuthorizationPolicyDelegate.updateRequest()
method:extension YourClass: AuthorizationPolicyDelegate { func updateRequest(originalRequest: URLRequest, txId: String?) -> URLRequest { // append txId into the request return request } }
If a delegation method is not defined, the SDK appends the
_txid
query parameter automatically to the URL.
Handle transactions in a JavaScript app
Transactional authorization is built into the HttpClient
module of the Ping SDK for JavaScript.
The HttpClient
module detects when transactional authorization is enabled, and depending on your setup, initiates interaction with either PingAM or IG.
Ensure you specify credentials: 'include'
, so that the request includes the necessary cookies.
When transactional authorization is enabled and callbacks are returned, your client app must implement the necessary user interaction. Ensure that you iterate through returned callbacks until you receive a success or failure response.
When you do receive a success response, make a new request to the initial resource endpoint, which will now be authorized.
The following code shows a sample JavaScript implementation. The included authorization
middleware handles the callbacks that the configured transactional authorization returns:
console.log('Make a $200 withdrawal from account');
return HttpClient.request({
init: {
method: 'POST',
body: JSON.stringify({ amount: '200' }),
credentials: 'include',
},
authorization: {
handleStep: async (step) => {
console.log('Withdrawal endpoint is set up for transational authorization...');
step.getCallbackOfType('ValidatedCreateUsernameCallback').setName(un);
step.getCallbackOfType('ValidatedCreatePasswordCallback').setPassword(pw);
return Promise.resolve(step);
},
},
timeout: 0,
url: `${resourceUrl}/withdraw`,
});