Transaction Signing

View this page for | |

Once the keys are provisioned, the device is ready to perform a Transaction Signing operation (that is, to approve or decline an authentication request operation based on details sent by the HID authentication platform). How the application is notified that a transaction is to be signed depends on its deployment. One option is to get a push notification from the server.

Pending Transactions

For Client Initiated Backchannel Authentication (CIBA) integrations and more information about creating a transaction using a Web App/backend integration, refer to the bcauthorize endpoint in the HID authentication platform documentation.

The integrating application signs a transaction as follows:

  1. Create an instance of the HIDDevice (HIDDeviceFactory.newInstance).
  2. Retrieve the transaction identifier (transactionId) for the transaction that will be processed. This identifier can be retrieved from the:
    • Push notification payload received by the application. This is the tds member of the payload

    • List of pending transactions for a specific container retrieved from the server (HIDContainer.retrieveTransactionIds)

    • Scanning a QR code encoded with a userID-less transaction

  3. Get public information (HIDServerActionInfo) from the transaction identifier (transactionId)(HIDDevice.retrieveActionInfo).

    For a userID-less transaction, anHIDInexplicitContainer (9) error containing the list of possible userIDs is raised if several containers exist for the targeted server. The integrating application will need to select a userID from the HID_ERROR_PARAMETERS list in the NSError userInfo dictionary and call HIDDevice.retrieveActionInfo with the userID parameter.

    There is no communication with the server at this point.

    The returned HIDServerActionInfo instance provides the:

  4. Check if the Session Transport Key is protected by a password and prompt the user as required.
  5. Get transaction details from the server (HIDServerActionInfo.getAction).
  6. Get the transaction details (HIDTransaction.toString) and the list of allowed statuses (HIDTransaction.getAllowedStatuses) that will be displayed to the end user so that they can decide which action to take (“approve” or “decline” the transaction).
  7. Display the transaction to the end user and retrieve the end user’s selection among the available statuses.
  8. Then request the end user to provide their Transaction Signing password and send the final status with optional mobile context data to the HID authentication platform (HIDTransaction.setStatus).

    For integrations with the HID Authentication Service, see User Authentication with HID Approve and the bcauthorize endpoint.

Sample Transaction Signing on iOS/macOS

Copy
do {
        // Get Device instance with default connection configuration
        let config = HIDConnectionConfiguration()
        let deviceFactory = HIDDeviceFactory.factory() as! HIDDeviceFactory
        let device = try deviceFactory.newInstance(config)
        
        // Get public information from the transaction identifier
        // the txId is obtainable either through the push message, or through the retrieveTransactionIds API of the container.
        let txInfo = try device.retrieveActionInfo(txId)
        
        // The public information includes the container and the transaction protection key.
        let container = try txInfo.getContainer()
        NSLog("Transaction is for container: %ld", container.getId());
        NSLog("Transaction unique id: %@", txInfo.getUniqueIdentifier());

        // We can check whether password is needed by getting the key and policy
        let key = try txInfo.getProtectionKey()
        NSLog("Transaction protection key is %@", key.getId().id);
        
        // Retrieve the transaction details
        // we assume session key is not password protected (password nil)
        let tx = try txInfo.getAction(nil, withParams: nil) as! HIDTransaction

        // The transaction contains the list of allowed response status
        let allowedStatus = tx.getAllowedStatuses()
        
        // Display transaction details to end user and request status
        NSLog("Transaction details %@", tx.toString());
        let txStatus = self.transactionPrompt(tx)
        
        // Here we can check whether the signing key is protected by a password
        var sigPassword: String?
        let sigKey = try tx.getSigningKey()
        let sigPolicy = try sigKey.getProtectionPolicy()
        if(sigPolicy.policyType()==HIDPolicyTypeBioPassword) {
            NSLog("Bio policy, check if it is enabled")
            let bioPolicy = sigPolicy as! HIDBioPasswordPolicy
            let bioState = bioPolicy.getBioAuthenticationState()
            if(bioState==HIDBioAuthenticationStateEnabled) {
                NSLog("Bio is enabled, password not needed")
                sigPassword = nil
            } else {
                NSLog("Bio is disabled, request password")
                sigPassword = self.passwordPrompt()
            }
        } else if(sigPolicy.policyType()==HIDPolicyTypePassword) {
            NSLog("Password policy, request password")
            sigPassword = self.passwordPrompt()
        } else {
            NSLog("No password")
            sigPassword = nil
        }
        
        // We can now sign the transaction with a selected status and context data
        let contextParam = HIDParameter(string: sContextB64, forKey: HID_PARAM_TX_MOBILE_CONTEXT)
        let params:[Any]? = [contextParam!]

        // we assume session key is not password protected (password nil)
        let isSigned = try tx.setStatus(txStatus, withSigningPassword: sigPassword, withSessionPassword: nil, withParams: params)
        NSLog("Transaction signed")
    }
    catch let error as NSError{
        NSLog("Failed to sign transaction: %@",error.localizedDescription);
    }
Copy
// Get Device instance with default connection configuration
    NSError* error;
    HIDConnectionConfiguration* connectionConfig = [[HIDConnectionConfiguration alloc] init];
    id<HIDDevice> pDevice = [[HIDDeviceFactory alloc] newInstance:connectionConfig error:&error];

    // Get public information from the transaction identifier
    // the txId is obtainable either through the push message, or through the retrieveTransactionIds API of the container.
    id<HIDServerActionInfo> txInfo = [pDevice retrieveActionInfo:txId error:&error];
    if (error)
    {
        NSLog(@"Failed to retrieve transaction info: %@ (%ld)",[error userInfo], (long)[error code]);
    }
    else {
        
        // The public information includes the container and the transaction protection key.
        id<HIDContainer> pContainer = [txInfo getContainer:&error];
        NSLog(@"Transaction is for container: %ld", (long)[pContainer getId]);
        NSLog(@"Transaction unique id: %@", [txInfo getUniqueIdentifier]);

        // We can check whether password is needed by getting the key and policy
        id<HIDKey> pKey = [txInfo getProtectionKey:&error];    
        NSLog(@"Transaction protection key is %@", [[pKey getId] ID]);
        
        // Retrieve the transaction details
        // we assume session key is not password protected (password nil)
        id<HIDTransaction> tx = ( id<HIDTransaction>)[txInfo getAction:nil withParams:nil error:&error];
        if (error) {
            NSLog(@"Failed to retrieve transaction: %@ (%ld)",[error description], (long)[error code]);
        }

        // The transaction contains the list of allowed response status    
        NSArray* allowedStatus = [tx getAllowedStatuses];
        
        // Display transaction details to end user and request status
        NSLog(@"Transaction details %@", [tx toString]);
        NSString* txStatus = [self transactionPrompt:tx];

        // Here we can check whether the signing key is protected by a password
        NSString* sigPassword = nil;
        id<HIDKey> pSigKey = [tx getSigningKey:&error];
        id<HIDProtectionPolicy>  sigPolicy = [pSigKey getProtectionPolicy:&error];
        if ([sigPolicy policyType] == HIDPolicyTypeBioPassword)
        {
            NSLog(@"Bio policy, check if it is enabled");
            id<HIDBioPasswordPolicy> bioPolicy = (id<HIDBioPasswordPolicy>)sigPolicy;
            HIDBioAuthenticationState bioState = [bioPolicy getBioAuthenticationState];
            if (bioState == HIDBioAuthenticationStateEnabled){
                NSLog(@"Bio is enabled, password not needed");
                sigPassword = nil;
            }
            else {
                NSLog(@"Bio is disabled, request password");
                sigPassword = [self passwordPrompt];
            }
        }
        else if ([sigPolicy policyType] == HIDPolicyTypePassword)
        {
            NSLog(@"Password policy, request password");
            sigPassword = [self passwordPrompt];
        }
        else {
            NSLog(@"No password");
            sigPassword = nil;
        }
        
        // We can now sign the transaction with a selected status and context data
        // optional parameter can be used to pass mobile context base64 data (otherwise leave empty or null)
        NSMutableArray* params = [NSMutableArray array];
        [params addObject:[HIDParameter parameterWithString:sContextB64 forKey: HID_PARAM_TX_MOBILE_CONTEXT]];

        // we assume session key is not password protected (password nil)
        BOOL isSigned = [tx setStatus:txStatus withSigningPassword:sigPassword withSessionPassword:nil withParams:params error:&error];
        if (error) {
            // HIDAuthentication if invalid password is given
            // HIDTransactionExpired if transaction is no longer valid
            // HIDPasswordExpired if password is expired and requires password change according to protection policy
            NSLog(@"Failed to sign transaction: %@ (%ld)",[error description], (long)[error code]);
        } else {
            NSLog(@"Transaction signed");
        }
    }

Direct Client Signature (DCS)

In combination with the HID Authentication Service, HID Approve SDK 5.14 introduces the Direct Client Signing (DCS) feature allowing to generate authentication requests for signature directly within the HID Approve SDK.

The symmetric workflow provides seamless integration for authentication for business applications and is a suitable and a more secure alternative for symmetric key-based OTPs. Depending on the use case, it could also eliminate the need for complex asynchronous integrations that use traditional pending transactions for authentication.

After the generation of a direct transaction, it can be signed the same way as standard pending transactions.

Optionally, the HID Approve SDK provides two verification methods of the authentication depending on the integration:

  • For immediate client-side verification in the integrating application, the OIDC ID Token can be used to perform additional verification. The ID token provides identity and authentication details with a digital signature to ensure authenticity and protect the integrity of the provided data.

    For further information, see HID Authentication Service documentation on OpenID Tokens.

  • For Client Initiated Backchannel Authentication (CIBA) integrations that might require additional verification of the authentication request signature, the newly created authentication request identifier will be returned after performing the cryptographic signature. For further information about CIBA feedback event polling, see Configuring the CIBA Feedback Mode.

For example:

  1. Create an instance of the Device (HIDDeviceFactory.getDevice).
  2. Locate a specific container instance (HIDDevice.findContainers).

  3. Locate the asymmetric signing key to be used with the new transaction. This can be done by using the filter KEY_PROPERTY_USAGE with either KEY_PROPERTY_USAGE_AUTH or KEY_PROPERTY_USAGE_SIGN (HIDContainer.findKeys).

  4. Generate a new transaction for a specific container (HIDContainer.generateAuthenticationRequest).

  5. Check if the Session Transport Key is protected by a password and prompt the user as required.

  6. Prompt the end user to provide their Transaction Signing password if protected by a password, and send the final status to the HID Authentication Service with the selected status (HIDTransaction.setStatus).

  7. The ID Token can be used for client-side verification as required (HIDTransaction.getIdToken).

  8. For CIBA integrations, forward the created request ID to the back end for verification as required (HIDTransaction.getRequestId).

Copy
 do {
        
    let filter = [HIDParameter.parameter(with: HID_KEY_PROPERTY_USAGE_SIGN, forKey: HID_KEY_PROPERTY_USAGE)!]
    let keys = try mycontainer.findKeys(filter)  as? [HIDKey]
      
    let key = keys?.first
    let id = key!.getId()
    let transaction = try mycontainer.generateAuthenticationRequest("my message", withKey: id);
        
    // We assume the key is not password protected. ( password null )
    try! transaction.setStatus("my status", withSigningPassword: nil, withSessionPassword: nil, withParams: nil)
    let reqid = try transaction.getRequestId();
    let tokeinid = try transaction.getIdToken();
        
}
catch {
    NSLog("Failed to sign a direct client signature transaction: %@",error.localizedDescription);
}
Copy
NSError* error;
NSMutableArray* filter = [[NSMutableArray alloc] init];
[filter addObject:[HIDParameter parameterWithString:HID_KEY_PROPERTY_USAGE_SIGN forKey:HID_KEY_PROPERTY_USAGE]];
NSArray* keys = [self.container findKeys:filter error:&error];
if (error)
{
    NSLog(@"Failed to find key: %@ (%ld)",[error userInfo], (long)[error code]);
}
else {
    id<HIDKey> pKey = keys[0];
    HIDIdentifier* ID  = [pKey getId];
    
    id<HIDTransaction>  transaction = [self.container generateAuthenticationRequest:@"my message" withKey:ID error:&error];
        
    if (error) {
        NSLog(@"Failed to generate a transaction: %@ (%ld)",[error description], (long)[error code]);
    }
        
    BOOL isSigned = [transaction setStatus:@"my status" withSigningPassword:nil withSessionPassword:nil withParams:nil error:&error];
    if (error) {
        NSLog(@"Failed to sign transaction: %@ (%ld)",[error description], (long)[error code]);
    } else {
        NSLog(@"Transaction signed");
    }
    NSString* requestId = [transaction getRequestId:&error];
    NSString* idtoken = [transaction getIdToken:&error];
}
Note: The DCS feature is only available with the HID Authentication Service. It is not supported by the ActivID Authentication Server or ActivID Appliance.

Canceling Pending Transactions

The HID Authentication Service now supports discarding or canceling pending transactions from the server without needing to sign the rejected transactions. For integrating applications, this can provide an improved user experience as the end-user’s signing key is not required to perform the cancel operation.

In addition, users can indicate an unwanted fraudulent or suspicious transaction via the application to the back end. Depending on the server authentication policy configuration, appropriate administrator-controlled counter measures can be invoked to reduce the risk of unauthorized access or potential push notification fatigue attacks on the end user.

For example, the authentication policy's disabledTimeReset parameter could be configured to prevent the creation of new transactions indefinitely or to block for a fixed 'cool-down' period. Any remaining pending transactions for the user would also be automatically revoked by the server.

Copy
 do {
    // Get Device instance with default connection configuration
    let config = HIDConnectionConfiguration()
    let deviceFactory = HIDDeviceFactory.factory() as! HIDDeviceFactory
    let device = try deviceFactory.newInstance(config)
    
    // Get public information from the transaction identifier
    // the txId is obtainable either through the push message, or through the retrieveTransactionIds API of the container.
    let txInfo = try device.retrieveActionInfo(txId)
        
    // The public information includes the container and the transaction protection key.
    let container = try txInfo.getContainer()
    NSLog("Transaction is for container: %ld", container.getId());
    NSLog("Transaction unique id: %@", txInfo.getUniqueIdentifier());
        
    // Retrieve the transaction details
    // we assume session key is not password protected (password nil)
    let tx = try txInfo.getAction(nil, withParams: nil) as! HIDTransaction

// We can cancel the transaction or cancel with flag suspicious
// Optional message describing cancelation reason to be audited
// Reason can be either USER_CANCEL or NOTIFY_SUSPICIOUS
try tx.cancel( "my message", withCancelationReason: HIDCancelationReasonCode.USER_CANCEL, withSessionPassword: nil)
}
catch let error as NSError{
    NSLog("Failed to cancel transaction: %@",error.localizedDescription);
}
Copy
// Get Device instance with default connection configuration
NSError* error;
HIDConnectionConfiguration* connectionConfig = [[HIDConnectionConfiguration alloc] init];
id<HIDDevice> pDevice = [[HIDDeviceFactory alloc] newInstance:connectionConfig error:&error];

// Get public information from the transaction identifier
// the txId is obtainable either through the push message, or through the retrieveTransactionIds API of the container.
id<HIDServerActionInfo> txInfo = [pDevice retrieveActionInfo:txId error:&error];
if (error)
{
    NSLog(@"Failed to retrieve transaction info: %@ (%ld)",[error userInfo], (long)[error code]);
}
else {
    // The public information includes the container and the transaction protection key.
    id<HIDContainer> pContainer = [txInfo getContainer:&error];
    NSLog(@"Transaction is for container: %ld", (long)[pContainer getId]);
    NSLog(@"Transaction unique id: %@", [txInfo getUniqueIdentifier]);

    // Retrieve the transaction details
    // we assume session key is not password protected (password nil)
    id<HIDTransaction> tx = ( id<HIDTransaction>)[txInfo getAction:nil withParams:nil error:&error];
    if (error) {
        NSLog(@"Failed to retrieve transaction: %@ (%ld)",[error description], (long)[error code]);
    }
        
    // We can cancel the transaction or cancel with flag suspicious
    // Optional message describing cancelation reason to be audited
    // Reason can be either USER_CANCEL or NOTIFY_SUSPICIOUS
    [tx cancel:@"my message" withCancelationReason:USER_CANCEL withSessionPassword:@"" error:&error];

    }