Decide pending Operations
You can start the decision flow using three methods, from a link, from a notification or from fetched pending events. First, you need to start a decision flow, there are three ways to do it:
Client requirements
Create a decision flow
- iOS (Swift)
- Android (Java)
// using passing the operation object
let decision = try trustfactorClient.startOperationDecisionFlow(for: <TFOperation.ID>, applicationProfileId: <TFApplicationProfile.ID>, context: .history)
// using a decision URL (context is infered as ".deeplink")
let decision = try trustfactorClient.startOperationDecisionFlow(url: <URL>)
// using a notification (context is infered as ".pushNotification")
let decision = try trustfactorClient.startOperationDecisionFlow(notification: <UNNotification>)
// using passing the operation object
TFOperationDecisionFlow decision = trustFactorClient.startOperationDecision(operationId, profileId, TFOperationDecisionFlow.Context.HISTORY);
// using a decision URL (context is infered as ".deeplink")
TFOperationDecisionFlow decision = trustFactorClient.startOperationDecision(decisionUri);
// using a notification (context is infered as ".pushNotification")
try{
byte [] operationData = trustFactorClient.parsePushNotificationUserInfo(notificationPayload);
} catch(Exception e){
// handle error
}
if(operationData == null || operationData.length == 0) {
// handle error
}
TFOperationDecisionFlow decision = trustFactorClient.startOperationDecision(operationData);
Get operations details
- iOS (Swift)
- Android (Java)
// get details: mandatory
decision.getOperationDetails() { result in
switch result {
case .success(let operation):
// handle success
case .failure(let error):
// handle error
}
}
// get details: mandatory
decision.getOperationDetails((result) -> result.fold(
(TFOperation operation, String correlationId) -> {
// handle success
},
(Error error, String correlationId) -> {
// handle error
}
));
Add checkpoints
Checkpoints are not mandatory but we encourage their usage.
- iOS (Swift)
- Android (Java)
// Add this checkpoint if/when a Summary of the operation is shown to the user.
decision.addCheckpoint(.didShowOperationSummary)
// Add this checkpoint when the full list of details is presented to the user.
decision.addCheckpoint(.didShowOperationDetails)
// Add this checkpoint if/when a Summary of the operation is shown to the user.
decision.addCheckpoint(TFOperationDecisionFlow.Checkpoint.DID_SHOW_OPERATION_SUMMARY);
// Add this checkpoint when the full list of details is presented to the user.
decision.addCheckpoint(TFOperationDecisionFlow.Checkpoint.DID_SHOW_OPERATION_DETAILS);
Authentication
Some Operations require the user to authenticate using a PIN, Biometry or both. It's important to keep in mind that the validation of the required authentication mechanisms is only done during the decision.
Note: There's no need to set authentications if the user is rejecting the operation.
- iOS (Swift)
- Android (Java)
// add PIN authentication
if operationDetails.requiredAuthentications?.contains(.pin) {
try decision.addAuthentication(.pin(<string>))
}
// add biometric authentication
if operation.requiredAuthentications?.contains(.biometrics) {
try decision.addAuthentication(.biometric(context: <LAContext>))
}
// Add PIN authentication
if (operation.getRequiredAuthenticationList().contains(TFAuthenticationMechanism.PIN)) {
try {
decision.addAuthentication(TFAuthentication.pin(pin));
} catch (Exception exception) {
// Handle the exception
}
}
// Add biometric authentication
// cipher must be obtained from getBiometricDecryptCipher() and authenticated via BiometricPrompt's CryptoObject
if (operation.getRequiredAuthenticationList().contains(TFAuthenticationMechanism.BIOMETRIC)) {
try {
decision.addAuthentication(TFAuthentication.biometric(cipher));
} catch (Exception exception) {
// Handle the exception
}
}
Precise location
An Operation may require the user to share a precise location with the decision — read it from the operation
details (TFOperation.requiresPreciseLocation on iOS, TFOperation.isPreciseLocationRequired() on Android). Only
approving needs one: a user can always reject an Operation, whatever it requires.
The fix has to be taken for this decision. The position shared through
updateLocation(_:) is the device's last known location, is kept without expiry and may
not have come from GPS, so it never satisfies the requirement.
Approving without a usable fix fails with operationDecision(.requirePreciseLocation) /
OPERATION_DECISION / REQUIRE_PRECISE_LOCATION — locally when the SDK can tell, and from TrustFactor itself
(E_400080018) otherwise. Unlike a wrong confirmation code, this leaves the
Operation pending, so the user can allow the permission and decide again.
- iOS (Swift)
- Android (Java)
// A location the app captured for this decision — CLLocationManager.requestLocation(), not a cached one.
if operation.requiresPreciseLocation {
decision.setPreciseLocation(<CLLocation>)
}
If your app declares NSLocationDefaultAccuracyReduced, a user who granted location access still gives you a
fuzzed position. Call
requestTemporaryFullAccuracyAuthorization(withPurposeKey:)
before capturing the fix, and declare the purpose key in NSLocationTemporaryUsageDescriptionDictionary.
// A location the app captured for this decision — not the last known one.
if (operation.isPreciseLocationRequired()) {
decision.setPreciseLocation(location);
}
Capturing the fix requires ACCESS_FINE_LOCATION. On Android 12+ (API 31) the user may grant only approximate
location even when the app asks for precise, and a fuzzed position does not satisfy the requirement.
Confirmation code
An Operation may require the user to pick a confirmation code. The client application shows the correct code on its own screen; TrustFactor sends the device a shuffled list of candidates and never says which one is right, so an attacker holding the device can only guess.
Read the options from the operation details, show them in the order they arrived, and submit the 1-based position of the one the user picked — never the code itself. Do not sort, filter or de-duplicate the list: the position is the answer.
Only approving needs a code; an Operation can always be rejected without one.
Every other decision-time requirement — PIN, biometrics, a precise location — leaves the Operation pending so the user can try again. A wrong confirmation code does the opposite: the Operation is recorded as failed, the client application is notified, and it cannot be decided again. Do not offer a retry; a new Operation has to be created.
Approving without picking anything therefore fails locally with
operationDecision(.requireConfirmationCode) / OPERATION_DECISION / REQUIRE_CONFIRMATION_CODE rather than being
sent — spending the user's only attempt on a question they were never asked would be the wrong outcome. A wrong
pick returns .wrongConfirmationCode / WRONG_CONFIRMATION_CODE (E_400080019).
- iOS (Swift)
- Android (Java)
if operation.requiresConfirmationCode {
// Show operation.confirmationCodeOptions in this order and let the user choose.
let picked: Int = <index of the option the user tapped> // 0-based, as displayed
decision.setConfirmationCodeOption(picked + 1) // submitted 1-based
}
if (operation.isConfirmationCodeRequired()) {
// Show operation.getConfirmationCodeOptions() in this order and let the user choose.
int picked = <index of the option the user tapped>; // 0-based, as displayed
decision.setConfirmationCodeOption(picked + 1); // submitted 1-based
}
Decision
- iOS (Swift)
- Android (Java)
decision.reject() { result, correlationId in
// handle result
}
// OR
decision.approve() { result, correlationId in
// handle result
}
decision.approve((result) -> result.fold(
(TFOperationDecisionResult value, String correlationId) -> {
// handle success
},
(Error error, String correlationId) -> {
// handle error
}
));
// OR
decision.reject((result) -> result.fold(
(TFOperationDecisionResult value, String correlationId) -> {
// handle success
},
(Error error, String correlationId) -> {
// handle error
}
));