Mobile authentication with push
Two factor authentication can be implemented using APNs push notifications. It's a very secure and user friendly way of authentication. You can use this feature to allow users to confirm their transactions. Confirmation can be done using simple Accept button, PIN, Biometric or a Custom Authenticator. All transaction information is encrypted and no sensitive information is sent through APNs.
Configuration
Before mobile authentication with push can be used, you should configure:
- Remote Notification Support in your app - you can configure it by following Apple push notification documentation
- Token Server - please follow Mobile authentication configuration guide
When your app and token server are configured for push notifications you should be able to enroll for mobile authentication with push and handle push requests using Onegini SDK.
Enrollment
Push mobile authentication enrollment requires regular mobile authentication enrollment to be done first. Only after it is completed, you can enroll for the push.
Enrollment for the mobile authentication with push can be described as follows:
1. APP -> APNS: Register for remote notifications.
2. APNS -> APP: The device token is returned.
3. APP -> SDK: Enroll for push by providing the device token and completion block.
4. SDK -> Token Server: Enroll push mobile authentication with device token.
5. Token Server -> SDK: Acknowledge the enrollment process is completed.
6. SDK -> APP: The result of the enrollment flow is reported by completion block.
The first step is to obtain the deviceToken
from APNs. Please follow the Apple remote notification
guide for instructions on how to obtain it.
The next step is to enroll with the device token using the enrollPushMobileAuth(with:completion:)
method
of the UserClient
instance:
userClient.enrollPushMobileAuth(with: deviceToken) { error in
guard let error = error else {
__ proceed without error
}
__ proceed with error
}
[[ONGUserClient sharedInstance] enrollForPushMobileAuthWithDeviceToken:deviceToken
completion:^(BOOL enrolled, NSError * _Nullable error) {}];
The result of the enrollment and, in case an error occurred, an error will be passed to the completion block.
Receiving requests
Once the user is enrolled he is able to receive push notifications. Onegini SDK represents them by PendingMobileAuthRequest
class. Those requests
do not contain any sensitive information, since those might be sent through the unsecure channels like APNS. However, pending mobile authentication requests
contain an identifier which is later used to fetch full mobile authentication request information which is represented by objects of a MobileAuthRequest
class.
From the PendingMobileAuthRequest
object you can get following information:
transactionId
- a unique identifier for each Mobile Authentication request.userProfile
- user profile for which mobile authentication request was sent.date
- the date when the mobile authentication request was sent.timeToLive
- time to live for which mobile authentication request was sent.message
- message specified by the portal when initiating the mobile authentication request.userInfo
- user info received from the APNS.
The Onegini SDK supports two ways of obtaining pending mobile authentication requests: APNS and Token Server.
APNS
The process of receiving a push from the APNS can be described as follows. In this flow we call the initiator of the mobile authentication request 'portal':
1. Portal -> Token Server: Initialize mobile authentication.
2. Token Server -> APNS: Send the push notification.
3. APNS -> Token Server: Push notification delivery report.
4. Token Server -> Portal: Identifier of the initialized mobile authentication transaction.
5. APNS -> APP: Deliver the push notification to the APP.
6. APP -> SDK: Parses the push notification received from the APNS.
When application receives a push notifiation from APNS it should be able to differentiate between different push notification messages. The reason why this responsibility is in the app is that one app can only have one push token. This allows the app to support more push messaging features besides mobile authentication provided by the Onegini SDK.
Push notification received from APNS is represented by a Dictionary
object which you can obtain from the UNNotificationResponse
object as follows:
func userNotificationCenter(_: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
handlePushMobileAuthenticationRequest(userInfo: response.notification.request.content.userInfo)
completionHandler()
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
NSDictionary *pushNotificationInfo = response.notification.request.content.userInfo
...
}
In case you are using the old API:
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
}
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo
{
NSDictionary *pushNotificationInfo = userInfo;
...
}
Before handling the push request it is required to verify that the request can be handled by the Onegini SDK. It can be done
by calling the pendingMobileAuthRequest(from:)
method. This method will try to parse the Dictionary
to PendingMobileAuthRequest
. If a push notification is intended for the SDK the
method will return PendingMobileAuthRequest
object with transactionId
and userProfile
. Otherwise, it will return nil
.
Example code for parsing push notification received from APNS:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
NSDictionary *pushNotificationInfo = response.notification.request.content.userInfo;
ONGPendingMobileAuthRequest *pendingMobileAuthRequest = [[ONGUserClient sharedInstance] pendingMobileAuthRequestFromUserInfo:pushNotificationInfo]];
...
completionHandler();
}
Token Server
When device did not receive sent push notifications you can use this feature to fetch a list containing pending mobile authentication requests. The process of receiving a push from the Token Server can be described as follows. In this flow we call the initiator of the mobile authentication request 'portal':
1. Portal -> Token Server: Initialize mobile authentication.
2. Token Server -> Portal: Identifier of the initialized mobile authentication transaction.
3. APP -> SDK -> Token: Fetch all pending mobile authentication requests
Fetching pending mobile authentication requests can be performed using the following method:
SharedUserClient.instance.pendingPushMobileAuthRequests { requests, error in
if error == nil {
__ Handle list containing pending mobile authentication requests
} else {
switch error.code {
case ONGGenericError.deviceDeregistered.rawValue:
__ The device registration was removed from the Token Server. All locally stored data
__ is removed from the device and the user needs to register again.
case ONGGenericError.configurationInvalid.rawValue:
__ The provided client credentials are not valid or the client is not a dynamic client.
default:
__ Other errors
}
}
[[ONGUserClient sharedInstance] pendingPushMobileAuthRequests:^(NSArray<ONGPendingMobileAuthRequest *> * _Nullable pendingTransactions, NSError * _Nullable error) {
if (error == nil) {
__ Handle list containing pending mobile authentication requests
} else {
switch (error.code) {
case ONGGenericErrorDeviceDeregistered:
__ The device registration was removed from the Token Server. All locally stored data
__ is removed from the device and the user needs to register again.
break;
case ONGGenericErrorConfigurationInvalid:
__ The provided client credentials are not valid or the client is not a dynamic client.
break;
default:
__ Other errors
break;
}
}
}];
When the user will not have any pending mobile authentication requests, then the method will return an empty list. The results of this request are sorted by the time sent (descending).
Request handling
Once the push request is received and parsed to PendingMobileAuthRequest
it is handled by calling the handlePendingPushMobileAuthRequest:delegate:
on the
UserClient
shared instance. Since notifications may require authentication, the SDK needs a delegate (MobileAuthRequestDelegate
) to authenticate.
The entire process of handling pending requests can be described as follows.
1. APP -> SDK: Handle pending mobile auth request.
2. SDK -> Token Server: Fetch PGP encrypted message from server.
3. Token Server -> SDK: Base64 encoded PGP encrypted authentication data.
4. SDK -> APP: The push message is decrypted and the SDK triggers a delegate so the app can show
a dialogue to the end-user.
5. APP -> SDK: Application responds with the authentication result.
6. SDK -> Token Server: The authentication result is send encrypted to the Token Server.
7 Token Server -> SDK: If the used push type requires feedback on the result e.g. pin usage
the result is communicated back to the SDK which will optionally perform a retry.
8. Token Server -> Portal: A callback is sent to inform the portal about the mobile
authentication result.
As you can see from the diagram above, the application has the following responsibilities during the request handling:
-
Responding to authentication challenges
- Displaying a dialog to the end-user when an authentication challenge is received
- Sending the users' response back to the SDK
-
Handling the completion of the mobile authentication request
The Following paragraphs explain those steps and show how the responsibilities mentioned above can be implemented.
Responding to a challenge
After you have passed mobile authentication request to the SDK, the SDK will request the app to let the user either confirm or deny the request.
The SDK will delegate control back to the provided MobileAuthRequestDelegate
implementation. Hence, the application
must contain a class that implements the methods specified below.
Mobile authentication requests are represented by MobileAuthRequest
class. Objects of this class are passed to
every MobileAuthRequestDelegate
method. From those obejcts you can get following information about currently
processes mobile authentication request:
userProfile
- instance ofUserProfile
for which request has been received.type
- string representing type of the notification which is configured in the Token Server admin panel. The type can be used to distinguish between business functionalities. For example, mobile authentication can be used for logging in or transaction approval.message
- message specified by the portal when initiating the mobile authentication request. This field represents thesecure_message
parameter sent while initiating the mobile authentication request. This message length is not limited.userInfo
- user info received from the APNS. TheuserInfo
can also contain a message. The message specified in theuserInfo
object represents themessage
parameter that was sent while initiating the mobile authentication request. The length of this message is limited to 155 characters due to APNS' message length limitations. The value is set only for push mobile authentications.transactionId
- A unique identifier for each Mobile Authentication request.
When a challenge is received you should display UI containing selected information from ONGMobileAuthRequest
and an
interface which allows user to respond to the received challenge e.g. PIN screen when PIN challenge was received.
The user's response should be send back to the SDK via the challenge sender object or through the confirmation block in
case of confirmation challenge.
Confirmation
func userClient(_ userClient: UserClient,
didReceiveConfirmationChallenge confirmation: @escaping (Bool) -> Void,
for request: MobileAuthRequest) {
__ Code to display your mobile authentication confirmation view here..
__ Once the user has answered the confirmation, call the confirm method specifying whether
__ the user confirms or denies the request
confirmation(true);
}
- (void)userClient:(ONGUserClient *)userClient
didReceiveConfirmationChallenge:(void (^)(BOOL confirmRequest))confirmation
forRequest:(ONGMobileAuthRequest *)request
{
__ Code to display your mobile authentication confirmation view here..
__ Once the user has answered the confirmation, call the confirm method specifying whether
__ the user confirms or denies the request
confirmation(YES);
}
PIN
func userClient(_ userClient: UserClient,
didReceivePinChallenge challenge: PinChallenge,
for request: MobileAuthRequest) {
__ Code to display the PIN view here..
__ Once the user has entered the PIN call the delegate
challenge.sender.respond(with: "pin", to: challenge)
__ Or cancel challenge
challenge.sender.cancel(challenge)
}
- (void)userClient:(ONGUserClient *)userClient
didReceivePinChallenge:(ONGPinChallenge *)challenge
forRequest:(ONGMobileAuthRequest *)request
{
__ Code to display the PIN view here..
__ Once the user has entered the PIN call the delegate
[challenge.sender respondWithPin:@"pin" challenge:challenge];
__ Or cancel challenge
[challenge.sender cancelChallenge:challenge];
}
Biometric
func userClient(_ userClient: UserClient,
didReceiveBiometricChallenge challenge: BiometricChallenge,
for request: MobileAuthRequest) {
__ Code to display the mobile authentication view here..
__ Continue Mobile Authentication using Fingerprint with custom prompt
challenge.sender.respond(with: "Confirm money transfer", to: challenge)
__ Fallback to pin if needed
challenge.sender.respondWithPinFallback(to: challenge)
__ Or cancel challenge
challenge.sender.cancel(challenge)
}
- (void)userClient:(ONGUserClient *)userClient
didReceiveBiometricChallenge:(ONGBiometricChallenge *)challenge
forRequest:(ONGMobileAuthRequest *)request
{
__ Code to display the mobile authentication view here..
__ Continue Mobile Authentication using Fingerprint with custom prompt
[challenge.sender respondWithPrompt:@"Confirm money transfer" challenge:challenge];
__ Fallback to pin if needed
[challenge.sender respondWithPinFallbackForChallenge:challenge];
__ Or cancel challenge
[challenge.sender cancelChallenge:challenge];
}
Depending on your needs the challenge
may be responded in a various ways: continue with
custom prompt, fallback to pin or even cancel. After you have responded to the challenge
, the SDK presents the TouchID
authentication with the supplied (or default) prompt. Since this method is optional, the SDK will automatically fallback
to PIN authentication if this method is not implemented.
Important: in order to receive biometric challenges you have to register the biometric authenticator first by following the Biometric authenticator topic guide.
Custom
func userClient(_ userClient: UserClient,
didReceiveCustomAuthFinishAuthenticationChallenge challenge: CustomAuthFinishAuthenticationChallenge,
for request: MobileAuthRequest) {
__ Code to display the mobile authentication view here..
__ Continue Mobile Authentication using a custom authenticator
challenge.sender.respond(with: customAuthData, to: challenge)
__ Fallback to pin if needed
challenge.sender.respondWithPinFallback(to: challenge)
__ Or cancel challenge
challenge.sender.cancel(challenge)
}
- (void)userClient:(ONGUserClient *)userClient
didReceiveCustomAuthFinishAuthenticationChallenge:(ONGCustomAuthFinishAuthenticationChallenge *)challenge
forRequest:(ONGMobileAuthRequest *)request
{
__ Code to display the mobile authentication view here..
__ Continue Mobile Authentication using a custom authenticator
[challenge.sender respondWithData:customAuthData forChallenge:challenge];
__ Fallback to pin if needed
[challenge.sender respondWithPinFallbackForChallenge:challenge];
__ Or cancel challenge
[challenge.sender cancelChallenge:challenge];
}
When you receive a Custom Authenticator challenge you can respond to it with optional data, pin fallback or cancellation.
Responding with data will continue authentication process, responding with pin will send pin challenge instead and cancellation
will deny the mobile authentication request.
Authentication with Custom Authenticator might fail with fallback to PIN. In this case CustomInfo
object is passed
under the CustomAuthInfoKey
in the userInfo of error object within the PinChallenge
.
Completion
Once the mobile authentication request has been handled there are two callbacks on the MobileAuthRequestDelegate
that might be called: userClient(_:request:authenticator:info:)
and
userClient(_:didFailToHandleMobileAuthenticationRequest:authenticator:error:)
for success and failure correspondingly. Both methods are
optionals.
For the success callback you may want to refresh your App's data or hide the view:
func userClient(_ userClient: UserClient,
didHandle request: MobileAuthRequest,
authenticator: Authenticator?,
info customAuthenticatorInfo: CustomInfo?) {
__ Hide the view, update application data, etc
}
- (void)userClient:(ONGUserClient *)userClient
didHandleMobileAuthenticationRequest:(ONGMobileAuthRequest *)request
authenticator:(ONGAuthenticator *_Nullable)authenticator
info:(ONGCustomInfo *_Nullable)customAuthInfo
{
__ Hide the view, update application data, etc
}
For the failure callback, the situation is a bit more complex. It is strongly recommended to implement error handling.
The SDK may deliver important errors such as user's (server-side) deregistration which requires special handling such as
user logout, unwinding the UI to the login page, etc. However, it also may deliver errors that can be ignored such as
the cancellation error which happens, when you cancel the challenge by calling the BiometricChallengeSender.cancel
methods.
In case of failure during authentication with Custom Authenticator returned error might include CustomInfo
object under
the CustomAuthInfoKey
in the userInfo.
func userClient(_ userClient: UserClient,
didFailToHandle request: MobileAuthRequest,
authenticator: Authenticator?,
error: Error) {
switch error.code {
case ONGGenericError.deviceDeregistered.rawValue:
__ The device registration was removed from the Token Server. All locally stored data
__ is removed from the device and the user needs to register again.
case ONGGenericError.userDeregistered.rawValue:
__ The user account is deregistered from the device. The user supplied the wrong PIN
__ for too many times. All local data associated with the user profile has been removed.
case ONGGenericError.actionCancelled.rawValue:
__ Requested action was cancelled.
case ONGGenericError.networkConnectivityFailure.rawValue:
case ONGGenericError.serverNotReachable.rawValue:
__ Networking issues.
case ONGMobileAuthRequestError.notFound.rawValue:
__ Mobile request was not found on the TS. We can either notify user or ignore it.
case ONGMobileAuthRequestError.notHandleable.rawValue:
__ Mobile request cannot be handled by the Onegini SDK.
case ONGMobileAuthRequestError.userDisenrolled.rawValue:
__ User that he was disenrolled for security reasons.
case ONGMobileAuthRequestError.notEnrolled.rawValue:
__ The user is not enrolled for mobile authentication.
default:
__ Other errors
}
}
- (void)userClient:(ONGUserClient *)userClient
didFailToHandleMobileAuthenticationRequest:(ONGMobileAuthRequest *)request
authenticator:(ONGAuthenticator *_Nullable)authenticator
error:(NSError *)error
{
switch (error.code) {
case ONGGenericErrorDeviceDeregistered:
__ The device registration was removed from the Token Server. All locally stored data
__ is removed from the device and the user needs to register again.
break;
case ONGGenericErrorUserDeregistered:
__ The user account is deregistered from the device. The user supplied the wrong PIN
__ for too many times. All local data associated with the user profile has been removed.
break;
case ONGGenericErrorActionCancelled:
__ Requested action was cancelled.
break;
case ONGGenericErrorNetworkConnectivityFailure:
case ONGGenericErrorServerNotReachable:
__ Networking issues.
break;
case ONGMobileAuthRequestErrorNotFound:
__ Mobile request was not found on the TS. We can either notify user or ignore it.
break;
case ONGMobileAuthRequestErrorNotHandleable:
__ Mobile request cannot be handled by the Onegini SDK.
break;
case ONGMobileAuthRequestErrorUserDisenrolled:
__ User that he was disenrolled for security reasons.
break;
case ONGMobileAuthRequestErrorNotEnrolled:
__ The user is not enrolled for mobile authentication.
break;
default:
__ Other errors
break;
}
}