OTPAuthenticator

public class OTPAuthenticator : AppDelegateObserving

OTPAuthenticator


The OTPAuthenticator component provides two factor authentication by accepting a one-time-pin code from the client. It is always paired with another type of authentication method.

Note: It is rare for a developer to access the OTPAuthenticator directly — you typically use the OTPObserver. Using the authenticator prior to receiving an OTP challenge from a SAPcpms endpoint results in an HTTP 404 response code on the server-side.

OTP is based on session cookies, the OTPAuthenticator uses a seperate web view to present a login page. The OTP in the SAP CP SDK for iOS supports UIWebView and WKWebView for authentication.

Default web view presenters are provided by the SDK: UIWebViewPresenter and WKWebViewPresenter. Custom web view presenter implementation is also possible with the use of UIWebViewPresenting or WKWebViewPresenting protocols.

Important: WKWebView based OTP only works with SAPHTTPCookieStorage! Make sure you have your SAPURLSession instance configured with the right HTTP cookie store!

Links:

Setting up an OTPAuthenticator

Using SAPcpms

  1. Create or obtain the SAPcpmsSettingsParameters.
  2. Create OTPAuthenticator with the settings parameters.
  3. (recommended) If you want to use the authenticator automatically on requests, then create an OTPObserver instance with the authenticator and register it to the SAPURLSession using the SAPURLSession‘s func register(_ observer: SAPURLSessionObserving ) method.

This sample code demonstrates how to set up the authenticator for custom usage:

let settingsParameters = SAPcpmsSettingsParameters( ... )
let authenticator = OTPAuthenticator(settingsParameters: settingsParameters)

Using advanced

  1. Create the OTPParameters with the required configuration.
  2. Create OTPAuthenticator with the authentication parameters.
  3. (recommended) If you want to use the authenticator automatically on requests, then create an OTPObserver instance with the authenticator and register it to the SAPURLSession using the SAPURLSession’s func register(_ observer: SAPURLSessionObserving ) method.

This sample code demonstrates how to set up the authenticator for custom usage:

let otpParameters = OTPParameters(authorizationEndpointURL: <#your authorizationEndpointURL#>, finishEndpointURL: <#your finishEndpointURL#>)
let authenticator = OTPAuthenticator(otpParameters: otpParameters)

// recommended step (3.)
let observer = OTPObserver(authenticator: authenticator)

// obtain urlSession
urlSession.register(observer)

Authorization endpoint URL

The authorizationEndpointURL is the URL which will be loaded into the web view. The required behaviour for this URL is to load the login page where the user can enter their credentials (pin code).

Finish endpoint URL

The finishEndpointURL is the URL which (when encountered in the web view) signals the end of the successful authentication flow. This means that the mechanism implemented on the authorizationEndpointURL must make a redirect to the finishEndpointURL upon successful authentication. When encountered with this URL in the web view, the web view disappears and the authenticate method’s completionHandler is called.

Usage

Starting the authentication flow

Calling the OTPAuthenticator’s func authenticate(session:completionHandler:) method authenticates the user at the given server. If there is an error during the process it is received in the completionHandler. If there is no error (the error property is nil) then the authentication was successful.

The authenticator displays a web view (using the default or custom-set web view presenter) on top of the view hierarchy on the main queue to show the login page.

authenticator.authenticate(session: session) { error in
    if let error = error {
        // If there was an error during the authentication process (web view presentation, or network related), it can be handled here.
        return
    }
    // If there was no error (error is `nil`) then the authentication was successful and the following requests to this endpoint should not encounter OTP challenges.
}

Note:

  • The first parameter must be an instance of SAPURLSession. The authentication process will put the acquired credentials (cookies) into this session’s cookie store.
  • There can be only one ongoing authentication process, which is guarded internally by the OTPAuthenticator. Simultaneous calls to the authenticate method results in a OTPError.authenticationInProgress error which is received in the completionHandler.

Automatic flow (SAPcpms)

When the OTP Form is presented, users have the option to open the SAP Authenticator application by tapping the link presented below the input field. This will open the authenticator application and if it will automatically reopen the host application with the OTP passcode (if configured properly). Using this method requires no user interaction beside the initial tap on the link.

To make your application trusted in the SAP Authenticator, you have to add it under your account. You can add applications via QR code or by entering the following URL in the correct section: your-application-id.xcallbackurl://x-callback-url/setCredential?x-source=com.sap.authenticator&username_paramname=j_username&username_paramvalue=[username]&passcode_paramname=j_passcode&passcode_paramvalue=[passcode]

The SAP Authenticator application will invoke the host application’s AppDelegate’s application(app:open:options:) method. The application developer must forward the parameters to the OTP component the following way:

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    return AppDelegateDispatcher.application(app, open: url, options: options)
}

You have to list the following URL Type in your Info.plist file to make possible for the SAP Authenticator to open your application. You can do it in the project settings: (project settings -> Info -> URL Types -> URL Schemes).

URL Type to list: your-application-id.xcallbackurl

In the Info.plist file it should look like this:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>your-application-id.xcallbackurl</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>your-application-id.xcallbackurl</string>
        </array>
    </dict>
</array>

Cancelling an ongoing authentication

You can cancel an ongoing authentication flow. The ongoing authentication receives a OTPError.cancelled error through the completionHandler and the web view disappears. This method does nothing if there was no ongoing authentication.

authenticator.cancelAuthentication()

Using your own web view presenter implementation

The OTP authentication requires a web view to present the login page. For this the authenticator needs to put a web view on the top of the current view hierarchy. The authenticator uses the UIWebViewPresenting protocol to achieve this which is implemented by default (UIWebViewPresenter).

If there is a custom requirement for presenting and designing a web view then you can use your own implementation of UIWebViewPresenting:

class MyWebViewPresenter: UIWebViewPresenting {
    weak var delegate: WebViewPresenterDelegate?
    weak var webViewDelegate: SAPUIWebViewDelegate?

    public func presentWebView(completionHandler: @escaping (UIWebView?, Error?) -> Void) {
        // TODO:
        // - acquire reference to `UIViewController`
        // - present the view controller which contains a `UIWebView`
        // - pass the `UIWebView` instance to the completionHandler
        // - pass an `Error` instance to the completionHandler if there was an error during presentation
    }

    func dismissWebView() {
        // TODO:
        // - acquire reference to presented `UIViewController` (or the presenting view controller)
        // - dismiss the presented view controller
    }
}

let presenter: UIWebViewPresenting = MyWebViewPresenter()
// Use the initializer which accepts custom web view presenters
let authenticator = OTPAuthenticator(otpParameters: <#OTPParameters#>, webViewPresenter: presenter)

From this point forward, the authenticator uses the newly set presenter to load the web view login page.

WKWebView based OTP only works with SAPHTTPCookieStorage. Make sure you have your SAPURLSession instance configured with it!

All calls to the web view presenter inside the authenticator happen on the main thread. You must call the completionHandler of your web view presenter implementation on the main thread!

Important: Be sure you call the completionHandler with the applicable objects when finished with the presentation.

The OTPAuthenticator component provides an API to conduct OTP authentication using a web view. By default the OTPAuthenticator will present a default ViewController which contains a web view to perform authentication. This can be replaced by a custom implementation of the UIWebViewPresenting protocol.

## This class is for advanced usage only! The SDK preferred way of implementing OTP authentication is the use of the OTPObserver. The direct usage or instantiation of this class is required only when:

  • the authentication is not against SAPcpms, therefore a custom authorization endpoint and finish endpoint URL is given
  • the authentication and network request has to be handled seperatly

Example of instantiating OTPAuthenticator with custom URLs:

 let otpParameters = OTPParameters(authorizationEndpointURL: <#your authorizationEndpointURL#>, finishEndpointURL: <#your finishEndpointURL#>)

 let authenticator = OTPAuthenticator(otpParameters: otpParameters)

Example of authenticating with custom OTPAuthenticator:

 authenticator.authenticate { error in
     if let error = error {
         // If there was an error during the authentication process (web view presentation, or network related), it can be handled here.
         return
     }
     // If there was no error (error is `nil`) then the authentication was successful and the following requests to this endpoint should not encounter OTP challenges.
 }
  • The external authenticator application’s identifier. This is the sourceApplication option in the AppDelegate‘s application(app:open:options) method.

    Declaration

    Swift

    public var externalAuthenticatorApplicationID: String
  • Starts the OTP authentication process. This method will present a web view on the main thread.

    Declaration

    Swift

    public func authenticate(session: SAPURLSession, completionHandler: @escaping (_ error: Error?) -> Void = OTPAuthenticator.defaultCompletionHandler)

    Parameters

    completionHandler

    The completion handler that gets invoked at the end of the authentication process. The default handler will log the error if there was any.

    error

    The Error happened during the process if there was any, otherwise nil. See OTPError for the list of possible OTP related errors.

  • Cancels the ongoing authentication process. The authenticate method’s completionHandler will be invoked with OTPError.cancelled error. Does nothing if there was no ongoing authentication.

    Declaration

    Swift

    public func cancelAuthentication()
  • Declaration

    Swift

    public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
  • Used in the autonomous OTP flow, this method creates the validation endpoint URL from the URLQueryItems obtained from the external application open URL. It is mapping the following query items: ‘username_paramvalue’, ‘passcode_paramvalue’ to ‘username’, ‘passcode’. The created validation endpoint URL will be loaded into the web view that is presenting the OTP form and waiting for the passcode.

    Declaration

    Swift

    public func validationEndpointURL(from queryItems: [URLQueryItem]) -> URL?

    Parameters

    queryItems

    query items obtained from the external application open URL

    Return Value

    the validation endpoint URL that will be loaded into the web view

  • The default completion handler for authentication. Logs the error with the root logger if there is any.

    Declaration

    Swift

    public static func defaultCompletionHandler(error: Error?)