Using Operations Service
- Introduction
- Creating an Instance
- Retrieve Pending Operations
- Start Periodic Polling
- Approve an Operation
- Reject an Operation
- Off-line Authorization
- Operations API Reference
- UserOperation
- Creating a Custom Operation
Introduction
Operations Service is responsible for fetching the operation list and for approving or rejecting operations.
An operation can be anything you need to be approved or rejected by the user. It can be for example money transfer, login request, access approval, …
Note: Before using Operations Service, you need to have a
PowerAuthSDK
object available and initialized with a valid activation. Without a valid PowerAuth activation, all endpoints will return an error
Operations Service communicates with a backend via Mobile Token API endpoints.
Creating an Instance
Factory Extension With SSL Validation Strategy
Convenience factory method that will return a new instance. A new OkHttpClient
will be created based on the chosen SSLValidationStrategy
in the last parameter.
fun PowerAuthSDK.createOperationsService(appContext: Context, baseURL: String, strategy: SSLValidationStrategy): IOperationsService
appContext
- application contextbaseURL
- address, where your operations server can be reachedstrategy
- strategy used when validating HTTPS requests. Following strategies can be used:SSLValidationStrategy.default
SSLValidationStrategy.noValidation
SSLValidationStrategy.sslPinning
Factory Extension With OkHttpClient
Convenience factory method that will return a new instance with provided OkHttpClient
that you can configure on your own.
fun PowerAuthSDK.createOperationsService(appContext: Context, baseURL: String, httpClient: OkHttpClient): IOperationsService
appContext
- application contextbaseURL
- address, where your operations server can be reachedhttpClient
-OkHttpClient
instance used for API requests
Retrieve Pending Operations
To fetch the list with pending operations, implement the IOperationsService
API, you can call:
operationsService.getOperations(object: IGetOperationListener {
override fun onSuccess(operations: List<UserOperation>) {
// render operations
}
override fun onError(error: ApiError) {
// render error state
}
})
After you retrieve the pending operations, you can render them in the UI, for example, as a list of items with a detail of operation shown after a tap.
Note: The language of the UI data inside the operation depends on the configuration of the IOperationsService.acceptLanguage
.
Start Periodic Polling
Mobile token API is highly asynchronous - to simplify the work for you, we added a convenience operation list polling feature:
// fetch new operations every 7 seconds periodically
if (!operationsService.isPollingOperations()) {
operationsService.startPollingOperations(7_000, false)
}
To receive the result of the polling, set up a listener.
Note that the listener is called for all “fetch operations” requests (not just the polling).
operationsService.listener = object: IOperationsServiceListener {
override fun operationsFailed(error: ApiError) {
// show UI the last fetch has failed
}
override fun operationsLoaded(operations: List<UserOperation>) {
// refresh operation list UI
}
override fun operationsLoading(loading: Boolean) {
// show loading UI
}
}
Approve an Operation
To approve an operation use IOperationsService.authorizeOperation
. You can simply use it with following examples:
// Approve operation with password
fun approve(operation: IOperation, password: String) {
val auth = PowerAuthAuthentication()
auth.usePossession = true
auth.usePassword = password
auth.useBiometry = null // needed only when approving with biometry
this.operationsService.authorizeOperation(operation, auth, object: IAcceptOperationListener {
override fun onSuccess() {
// show success UI
}
override fun onError(error: ApiError) {
// show error UI
}
})
}
To approve offline operations with biometry, your PowerAuth instance need to be configured with biometry factor.
// Approve operation with biometry
fun approveWithBiometry(operation: IOperation) {
// UserOperation contains information if biometry can be used
if (operation is UserOperation) {
if (!operation.allowedSignatureType.factors.contains(AllowedSignatureType.Factor.POSSESSION_BIOMETRY)) {
return
}
}
this.powerAuthSDK.authenticateUsingBiometry(appContext, fragmentManager,
"Operation approval",
"Use biometry to approve the operation",
object : IBiometricAuthenticationCallback {
override fun onBiometricDialogSuccess(biometricKeyData: BiometricKeyData) {
val auth = PowerAuthAuthentication()
auth.usePossession = true
auth.useBiometry = biometricKeyData.derivedData
this.operationsService.authorizeOperation(operation, auth, object: IAcceptOperationListener {
override fun onSuccess() {
// show success UI
}
override fun onError(error: ApiError) {
// show error UI
}
})
}
override fun onBiometricDialogCancelled(userCancel: Boolean) {
// the biometry dialog was canceled
}
override fun onBiometricDialogFailed(error: PowerAuthErrorException) {
// biometry authentication failed
}
}
)
}
Reject an Operation
To reject an operation use IOperationsService.rejectOperation
. Operation rejection is confirmed by possession factor so there is no need for creating PowerAuthAuthentication
object. You can simply use it with the following example.
// Reject operation with some reason
fun reject(operation: IOperation, reason: RejectionReason) {
this.operationsService.rejectOperation(operation, reason, object: IRejectOperationListener {
override fun onSuccess() {
// show success UI
}
override fun onError(error: ApiError) {
// show error UI
}
})
}
Operation History
You can retrieve an operation history via the IOperationsService.getHistory
method. The returned result is operations and their current status.
// Retrieve operation history with password
func history(password: String) {
val auth = PowerAuthAuthentication()
auth.usePossession = true
auth.usePassword = password
this.operationService.getHistory(auth, object : IGetHistoryListener {
override fun onSuccess(operations: List<OperationHistoryEntry>) {
// process operation history
}
override fun onError(error: ApiError) {
// process error
}
})
}
Note that the operation history availability depends on the backend implementation and might not be available. Please consult this with your backend developers.
Off-line Authorization
In case the user is not online, you can use off-line authorizations. In this operation mode, the user needs to scan a QR code, enter PIN code or use biometry, and rewrite the resulting code. Wultra provides a special format for the operation QR codes, that are automatically processed with the SDK.
Processing scanned QR operation
@Throws(IllegalArgumentException::class)
fun onQROperationScanned(scannedCode: String): QROperation {
// retrieve parsed operation
val operation = QROperationParser.parse(scannedCode)
// verify the signature against the powerauth instance
val verified = this.powerAuthSDK.verifyServerSignedData(operation.signedData, operation.signature.signature, operation.signature.isMaster())
if (!verified) {
throw IllegalArgumentException("Invalid offline operation")
}
return operation
}
Authorizing scanned QR operation
An offline operation needs to be always approved with 2-factor scheme (password or biometry).
With password
// Approves QR operation with password
fun approveQROperation(operation: QROperation, password: String) {
val auth = PowerAuthAuthentication()
auth.usePossession = true
auth.usePassword = password
try {
val offlineSignature = this.operationsService.authorizeOfflineOperation(operation, auth)
// Display the signature to the user so it can be manually rewritten.
// Note that the operation will be signed even with the wrong password!
} catch (e: Exception) {
// Failed to sign the operation
}
}
An offline operation can and will be signed even with an incorrect password. The signature cannot be used for manual approval in such a case. This behavior cannot be detected, so you should warn the user that an incorrect password will result in an incorrect “approval code”.
With biometry
To approve offline operations with biometry, your PowerAuth instance need to be configured with biometry factor.
If biometry can be used for offline operation authorization is determined by QROperation.flags.biometryAllowed
.
// Approves QR operation with biometry
fun approveQROperationWithBiometry(operation: QROperation, appContext: Context, fragmentManager: FragmentManager) {
if (!operation.flags.biometryAllowed) {
// biometry usage is not allowed on this operation
return
}
this.powerAuthSDK.authenticateUsingBiometry(appContext, fragmentManager,
"Operation approval",
"Use biometry to approve the operation",
object : IBiometricAuthenticationCallback {
override fun onBiometricDialogSuccess(biometricKeyData: BiometricKeyData) {
val auth = PowerAuthAuthentication()
auth.usePossession = true
auth.useBiometry = biometricKeyData.derivedData
try {
val offlineSignature = operationsService.authorizeOfflineOperation(operation, auth)
// Display the signature to the user so it can be manually rewritten.
} catch (e: Exception) {
// Failed to sign the operation
}
}
override fun onBiometricDialogCancelled(userCancel: Boolean) {
// the biometry dialog was canceled
}
override fun onBiometricDialogFailed(error: PowerAuthErrorException) {
// biometry authentication failed
}
}
)
}
Operations API Reference
All available methods and attributes of IOperationsService
API are:
listener
- Listener object that receives info about operation loading.acceptLanguage
- Language settings, that will be sent along with each request. The server will return properly localized content based on this value. Value follows standard RFC Accept-LanguagegetLastOperationsResult()
- Cached last operations result.isLoadingOperations()
- Indicates if the service is loading operations.getOperations(listener: IGetOperationListener?)
- Retrieves pending operations from the server.listener
- Called when operation finishes.
isPollingOperations()
- If the app is periodically polling for the operations from the server.startPollingOperations(pollingInterval: Long, delayStart: Boolean)
- Starts periodic operation polling.pollingInterval
- How often should operations be refreshed.delayStart
- When true, polling starts after the firstpollingInterval
time passes.
stopPollingOperations()
- Stops periodic operation polling.getHistory(authentication: PowerAuthAuthentication, listener: IGetHistoryListener)
- Retrieves operation historyauthentication
- PowerAuth authentication object for signing.listener
- Called when rejection request finishes.
authorizeOperation(operation: IOperation, authentication: PowerAuthAuthentication, listener: IAcceptOperationListener)
- Authorize provided operation.operation
- An operation to approve, retrieved fromgetOperations
call or created locally.authentication
- PowerAuth authentication object for operation signing.listener
- Called when authorization request finishes.
rejectOperation(operation: IOperation, reason: RejectionReason, listener: IRejectOperationListener)
- Reject provided operation.operation
- An operation to reject, retrieved fromgetOperations
call or created locally.reason
- Rejection reason.listener
- Called when rejection request finishes.
fun authorizeOfflineOperation(operation: QROperation, authentication: PowerAuthAuthentication)
- Sign offline (QR) operationoperation
- Offline operation retrieved viaQROperationParser.parse
method.authentication
- PowerAuth authentication object for operation signing.
signOfflineOperationWithBiometry(biometry: ByteArray, offlineOperation: QROperation)
- Sign offline (QR) operation with biometry data.biometry
- Biometry data retrieved frompowerAuthSDK.authenticateUsingBiometry
call.offlineOperation
- Offline operation retrieved viaprocessOfflineQrPayload
method.
UserOperation
Operations objects retrieved through the getOperations
API method (like getOperations
method in IOperationsService
) are called “user operations”.
Under this abstract name, you can imagine for example “Login operation”, which is a request for signing in to the online account in a web browser on another device. In general, it can be any operation that can be either approved or rejected by the user.
Visually, the operation should be displayed as an info page with all the attributes (rows) of such operation, where the user can decide if he wants to approve or reject it.
Definition of the UserOperations
:
class UserOperation: IOperation {
// Unique operation identifier
val id: String
// System name of the operation.
//
// This property lets you adjust the UI for various operation types.
// For example, the "login" operation may display a specialized interface with
// an icon or an illustration, instead of an empty list of attributes,
// "payment" operation can include a special icon that denotes payments, etc.
val name: String
// Actual data that will be signed.
val data: String
// Date and time when the operation was created.
val created: ZonedDateTime
// Date and time when the operation will expire.
val expires: ZonedDateTime
// Data that should be presented to the user.
val formData: FormData
// Allowed signature types.
//
// This hints if the operation needs a 2nd factor or can be approved simply by
// tapping an approve button. If the operation requires 2FA, this value also hints if
// the user may use the biometry, or if a password is required.
val allowedSignatureType: AllowedSignatureType
}
Definition of FormData
:
class FormData {
/// Title of the operation
val title: String
/// Message for the user
val message: String
/// Other attributes.
///
/// Each attribute presents one line in the UI. Attributes are differentiated by type property
/// and specific classes such as NoteAttribute or AmountAttribute.
val attributes: List<Attribute>
}
Attributes types:
AMOUNT
like “100.00 CZK”KEY_VALUE
any key value pairNOTE
just likeKEY_VALUE
, emphasizing that the value is a note or messageHEADING
single highlighted text, written in a larger font, used as a section headingPARTY_INFO
providing structured information about third-party data (for example known eshop)
Creating a Custom Operation
In some specific scenarios, you might need to approve or reject an operation that you received through a different channel than getOperations
. In such cases, you can implement the IOperation
interface in your custom class and then feed created objects to both authorizeOperation
and rejectOperation
methods.
Note: For such cases, you can use concrete convenient class LocalOperation
, that implements this interface.
Definition of the IOperation
:
interface IOperation {
/**
* Operation identifier
*/
val id: String
/**
* Data for signing
*/
val data: String
}