User authentication

This topic guide explains all the steps required to add user registration and authentication to your app. In case you didn't add the SDK to your application project yet, please follow the steps in the setting up the project topic guide.

The SDK is accompanied by a separate Example App which provides a basic implementation and shows how to interact with the SDK. The SDK relies on the app to perform certain tasks which can not be performed by the SDK itself.

The Example App demonstrates the essential steps which must be performed by the App developer.

User authentication is performed using the ONGUserClient. The instance can be obtained by using @property (nonatomic, readonly) ONGUserClient *userClient; of the ONGClient object or by calling[ONGUserClient sharedInstance] method. Attempt to obtain ONGUserClient without configuring the SDK first, will result in throwing NSInternalInconsistencyException exception.

The SDK does not provide any UI components but leaves this up to the app. The result of every operation on the ONGUserClient is reported back to the App via a delegate. Hence, the SDK does provides delegate interfaces that must be implemented by the app. All callbacks are performed on the main queue so the app developer can invoke methods on UIKit components directly without having to deal with queue management. These delegates are called once a specific action is required to be taken by the app or to give the result of a requested action back to the app.

Introduction

The SDK uses the OAuth 2.0 protocol to authenticate the device to access protected resources. To support this protocol the SDK acts as an OAuth 2.0 client.

Register user

User registration is one of the first things that you need to take care of. User registration essentially means connecting a user account to a mobile application installed on a device. It is possible to register multiple user accounts with the same application. If multiple users need to be registered the registration flow that is described below needs to be triggered once for every user. There is one restriction: you cannot register the same user multiple times. If you try to register the same user account twice you will essentially override the first registration for that user. Hence, it will not fail but it will reuse the profile that was already created for that user on the device.

The ONGUserClient offers a method to register a new user profile. The new user profile value object will be generated during the authentication process and will be returned in the success callback.

This methods is called registerUser:delegate: and requires two arguments:

  • scopes (NSArray<NSString *> *) the scopes authentication is requested for, when no scopes are requested the default scopes of the application will be used,
  • delegate (ONGRegistrationDelegate) the delegate delegates control back to the app in case it needs to do something or in case of a successful or failed registration attempt. The delegate must implement the methods specified in the ONGRegistrationDelegate class.

The example below shows you how to call the registerUser method and let the calling class also implement the required delegate:

[[ONGUserClient sharedInstance] registerUser:@[@"read"] delegate:self];

- (void)userClient:(ONGUserClient *)userClient didRegisterUser:(ONGUserProfile *)userProfile
{
    // The user is successfully registered
}

Handling Registration Request Challenge

When registering a user the application will receive a registration request challenge containing a URL, which must be handled. The challenge will be passed as an argument to userClient:didReceiveRegistrationRequestChallenge: method of your ONGRegistrationDelegate. This URL will redirect user to a web page where he can authenticate to his user account. When authentication is successful page will redirect you to your Redirect URL defined in token server application configuration.

The userClient:didReceiveRegistrationRequestChallenge: method has two parameters:

  • userClient (ONGUserClient) user client performing registration.
  • challenge (ONGRegistrationRequestChallenge) challenge containing URL used to perform a registration code request.

Challenge object represents registration request challenge. It provides all information about the challenge and the sender awaiting for the response:

  • userProfile (ONGUserProfile) user profile for which registration request challenge was sent.
  • url (NSURL) URL used to perform a registration code request.
  • error (NSError) error describing cause of failure of previous challenge response. Possible error domains: ONGGenericErrorDomain.
  • sender (id<ONGCreatePinChallengeSender>) sender awaiting for response to the registration request challenge.

You can respond to the challenge using sender object by calling one of the following methods:

  • respondWithURL:challenge: - used to deliver the redirection URL to the SDK
  • cancelChallenge - used for registration cancelation

It's up to you how you want to handle this URL, however it's recommended to use a web browser like Safari or preferably embedded web browser like UIWebView.

We recommend to use an embedded UIWebView to open this URL because this is the least disruptive for the end-user and also benefits from the additional security measures included in the SDK. The SDK from release 2.3 and upwards is updated to intercept the URL requests from the embedded UIWebView and perform certificate pinning.

Handling Registration Request URL with UIWebView

You can perform request using UIWebView by using its loadRequest method.

    NSURLRequest *request = [NSURLRequest requestWithURL:self.registrationRequestChallenge.url];
    [self.webView loadRequest:request];

Web view should guide the user through authentication process and then redirect back to the application using the Redirect URL. In order to intercept this redirection its recommended to implement webView:shouldStartLoadWithRequest:navigationType: method on your UIWebViewDelegate. Example implementation:

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    if ([request.URL.absoluteString hasPrefix:[OneginiConfigModel configuration][@"ONGRedirectURL"]]) {
        [self.registrationRequestChallenge.sender respondWithURL:request.URL challenge:self.registrationRequestChallenge];
        [self.navigationController dismissViewControllerAnimated:YES completion:nil];
        return NO;
    }
    return YES;
}

It's up to you when the web view will be closed. You could close it as soon as challenge sender receives redirection url, which is shown in the example or when yours ONGRegistrationDelege receives userClient:didReceivePinRegistrationChallenge:.

Handling Registration Request URL with External Web Browser

In order to handle registration request using external web browser first you need to configure custom URL scheme.

After the user has authenticated he/she is redirected back to the app using a custom URL scheme. The app must add an active URL scheme to the info.plist file. Administration of the redirect URL (that includes the custom URL scheme) is done in the application configuration in the Token Server admin console.

Below you can see the contents of the URL Type configuration in Xcode:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.onegini.OneginiSDKiOSTestApp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>oneginisdk</string>
</array>
</dict>
</array>
</dict>
</plist>

Next, you need to open the URL using external web browser. You can you use openURL method of UIApplication shared instance.

- (void)userClient:(ONGUserClient *)userClient didReceiveRegistrationRequestChallenge:(ONGRegistrationRequestChallenge *)challenge
{
    [[UIApplication sharedApplication] openURL:challenge.url];
}

Redirection using a custom URL scheme must be implemented within your UIApplicationDelegate object. It's done by implementing one of the following methods:

  • application:openURL:options: - available from iOS 9
  • application:handleOpenURL: - deprecated, but available before iOS 9
  • application:openURL:sourceApplication: - deprecated, but available before iOS 9

Example implementation:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
    if ([request.URL.absoluteString hasPrefix:[OneginiConfigModel configuration][@"ONGRedirectURL"]]) {
        [self.registrationRequestChallenge.sender respondWithURL:request.URL challenge:self.registrationRequestChallenge];
        return YES;
    }
    return NO;
}

Choose a PIN

The next step in the registration process is letting the user select a PIN. This process is triggered by the SDK. It delegates control over to the app by calling the userClient:didReceivePinRegistrationChallenge: method on the ONGRegistrationDelegate.

The userClient:didReceivePinRegistrationChallenge: method has two parameters:

  • userClient (ONGUserClient) user client performing registration.
  • challenge (ONGCreatePinChallenge) create pin challenge used to complete registration.

Challenge object represents create PIN challenge. It provides all information about the challenge and the sender awaiting for the response:

  • userProfile (ONGUserProfile) user profile for which create PIN challenge was sent.
  • pinLength (NSUInteger) required length for a new PIN.
  • error (NSError) error describing cause of failure of previous challenge response. Possible error domains: ONGPinValidationErrorDomain, ONGGenericErrorDomain.
  • sender (id<ONGCreatePinChallengeSender>) sender awaiting for response to the create PIN challenge.

Response to the create PIN challenge is sent using respondWithCreatedPin:challenge: method of the challenge sender.

Example implementation of userClient:didReceivePinRegistrationChallenge: delegate method:

- (void)userClient:(ONGUserClient *)userClient didReceivePinRegistrationChallenge:(ONGCreatePinChallenge *)challenge
{
    [self askUserToCreatePin:^(NSString *createdPin) {
        // Once the user has entered the PIN call the delegate
        [challenge.sender respondWithCreatedPin:createdPin challenge:challenge];
        // Or cancel challenge
        [challenge.sender cancelChallenge:challenge];
    }];
}

The respondWithCreatedPin:challenge: method of the ONGCreatePinChallengeSender needs to be called in order to let the SDK process the provided PIN. The SDK might trigger an error in case the chosen PIN is invalid. This could be because it is not compliant with PIN policy for example. The possible errors are specified in the ONGPinValidationErrorDomain.

Below follows an example implementation of challenge error handling:

- (void)userClient:(ONGUserClient *)userClient didReceivePinRegistrationChallenge:(ONGCreatePinChallenge *)challenge
{
    // Once the user has entered the PIN call the delegate
    [self askUserToCreatePin:^(NSString *createdPin) {
        [challenge.sender respondWithCreatedPin:createdPin challenge:challenge];
    };
    NSString *errorDescription = nil;

    if ([challenge.error.domain isEqualToString:ONGPinValidationErrorDomain]) {
        switch (challenge.error.code) {
            case ONGPinValidationErrorPinBlackListed:
                errorDescription = @"Provided PIN was marked as blacklisted on the Token Server.";
                break;

            case ONGPinValidationErrorPinShouldNotBeASequence:
                errorDescription = @"Provided PIN contains a not allowed sequence";
                break;

            case ONGPinValidationErrorPinTooShort:
                errorDescription = @"Provided PIN is too short";
                break;

            case ONGPinValidationErrorPinShouldNotUseSimilarDigits: {
                NSNumber *maxSimilarDigits = error.userInfo[ONGPinValidationErrorMaxSimilarDigitsKey];
                errorDescription = [NSString stringWithFormat:@"A PIN with less %zd similar digits must be chosen", maxSimilarDigits];
            }
                break;
        }   
    } else {
        // general error 
        errorDescription = error.localizedDescription;
    }

    // show error to user
    }

The PIN must not be stored anywhere on the device as this creates a security issue since an attacker potentially could extract it from the device.

Note: See the PIN handling recommendations for more information about handling the user PIN correctly.

After the user has successfully provided the PIN, the registration process is finished. One of the following methods on your implementation of the ONGRegistrationDelegate will be called, depending on the outcome of the registration process.

A successful outcome of the user registration will result in a user profile object. This object is the connection between the user and the registered profile in the SDK. We recommend to ask the end-user to provide a human readable reference to this profile (e.g. a name).

In order to initiate authentication this user profile object needs to be provided to the SDK, therefore it is recommended to let the user choose a name for his profile so he can easily remember which user account it is linked to.

- (void)userClient:(ONGUserClient *)userClient didRegisterUser:(ONGUserProfile *)userProfile
{
    // the user is successfully registered
}

- (void)userClient:(ONGUserClient *)userClient didFailToRegisterWithError:(NSError *)error
{
    // handle authentication error
}

Using an external browser

The SDK provides an application with the possibility to use an external browser to show the user identification process in. This can be configured by calling the setUseEmbeddedWebView: method of the ONGClientBuilder with a NO value. By default the SDK uses an embedded webview since it is in accordance with the Apple guidelines.

Note: Apple will deny your app if the first thing that it does is open an external browser.

Authenticate user

The ONGUserClient offers a method to initialize user authentication for a specific profile. The result of successful invocation of the authentication process is an access token. With this token the device can communicate with a back-end. In case the user fails to authenticate due to surpassing the maximum number of allowed PIN attempts the user will be deregistered due to security reasons. The maximum number of failed PIN attempts can be configured in the Token Server admin console.

If the user is already logged in, calling authenticateUser:delegate: will result in the SDK immediately calling the delegate method userClient:didAuthenticateUser: without prompting the user to authenticate.

The authenticateUser:delegate: method requires two arguments:

  • userProfile (ONGUserProfile) The user profile for which authentication must be performed. It contains an identifier that tells the SDK for which user profile you want to initiate authentication. Please read the documentation regarding user registration more carefully to find out what the user profile is for and how to use it.
  • delegate (ONGAuthenticationDelegate) The delegate delegates control back to the app in case it needs to do something or in case of a successful or failed authentication attempt. The delegate must implement the methods specified in the ONGAuthenticationDelegate class.

The example below shows you how to call the authenticateUser method and let the calling class also implement the required delegate:

[[ONGUserClient sharedInstance] authenticateUser:user delegate:self];

After initializing authentication the user needs to be asked to authenticate. It depends on the available authentication method which one will be initiated. In the example below we consider the default method: PIN. PIN authentication can always be used as the fallback authentication method. This is also the reason why the user must choose a PIN during the registration process

Provide PIN authentication

When the user needs to be asked to provide his/her PIN the SDK will delegate control back to the application by calling the userClient:didReceivePinChallenge: method of the class that implements the methods specified in the ONGAuthenticationDelegate.

The userClient:didReceivePinChallenge: method has two parameters:

  • userClient (ONGUserClient) user client performing authentication.
  • challenge (ONGCreatePinChallenge) pin challenge used to complete authentication.

Challenge object represents authenticate with PIN challenge. It provides all information about the challenge and the sender awaiting for the response:

  • userProfile (ONGUserProfile) user profile for which authenticate with PIN challenge was sent.
  • maxFailureCount (NSUInteger) maximum allowed pin attempts for the user.
  • previousFailureCount (NSUInteger) pin attempts used by the user on this device.
  • remainingFailureCount (NSUInteger) available pin attempts left for the user on this device.
  • error (NSError) error describing cause of failure of previous challenge response. Possible error domains: ONGPinAuthenticationErrorDomain, ONGGenericErrorDomain.
  • sender (id<ONGCreatePinChallengeSender>) sender awaiting for response to the authenticate with PIN challenge.

Response to the authenticate with PIN challenge is sent using respondWithPin:challenge: method of the challenge sender. In case the provided PIN is invalid SDK will resend the challenge with an error.

Below you can see an example implementation of this method:

- (void)userClient:(ONGUserClient *)userClient didReceivePinChallenge:(ONGPinChallenge *)challenge
{
    // Code to display your PIN view here..
    if (challenge.error) {
        //show remaining pin attempts
    }
    [self askUserForPin:^(NSString *pin) {
        // Once the user has entered the PIN call the delegate
        [challenge.sender respondWithPin:pin challenge:challenge];
        // Or cancel challenge
        [challenge.sender cancelChallenge:challenge];
    }];
}

Force authentication

Whenever there is a need to force a user to re-login, regardless whether he is already logged in or not, reauthenticateUser:delegate: must be called on the ONGUserClient instance.

The reauthenticateUser:delegate: method works exactly the same as the authenticateUser:delegate: with the exception that it will always ask the user to login again regardless of the previous state of the SDK.

Example code to force the user to authenticate:

[[ONGUserClient sharedInstance] reauthenticateUser:user delegate:self];

Logout user

In the SDK a user is marked as logged in as long as a valid access token is available within the device memory. To logout, the app calls the -[ONGUserClient logoutUser:] method. This will clear the access token from the current session and informs the Token Server to ensure the token is invalided on both client and server side.

The logoutUser: method requires one argument:

  • completion (void (^)(ONGUserProfile *userProfile, NSError *error)) the completion block that notifies caller about result of the logout action.

Example code to trigger user logout:

[[ONGUserClient sharedInstance] logoutUser:logoutCompletiont];

Based on the outcome of the logout action completion can be handled in next way.

[[ONGUserClient sharedInstance] logoutUser:^(ONGUserProfile *userProfile, NSError *_Nullable error) {
    if (error == nil) {
        // Code invoked after the user is logged out..    
    } else {
        // The access token is not revoked on the server side but the user is logged out locally (on the device) only..
    }
}];

Deregister user

User deregistration implies the removal of all access and refresh tokens from the device for this user account. The SDK will also send a request to the token server to revoke all tokens server side.

The deregisterUser:completion: method requires two arguments:

  • userProfile (ONGUserProfile) the user profile that must be deregistered
  • completion (void (^)(BOOL deregistered, NSError *error)) the completion block that notifies caller about result of the deregistration action and error (if any). Possible error domains are ONGDeregistrationErrorDomain and ONGGenericErrorDomain.

The example below shows you how to call the deregisterUser:completion: method:

[[ONGUserClient sharedInstance] deregisterUser:user completion:^(BOOL deregistered, NSError * _Nullable error) {
    if (deregistered) {
        // Code invoked after the user is deregistered..
    } else {
        // depending on error reason the user has been deregistered locally but the communication with the server failed.
    }
}];

Forgot PIN

The SDK does not support a method to recover a PIN for a user account simply because it cannot do so. The refresh token is stored encrypted on the device. For encryption of the refresh token the PIN is used as part of the encryption key. When a user forgets the PIN the existing refresh token used to log in cannot be decrypted and is useless in that sense. After a number of wrong PIN attempts the refresh token is removed automatically on both the SDK and on the server side. The max number of PIN retries must be configured in the application configuration in the Token Server admin console. To provide the user with an option to skip the max number of failed attempts before the refresh token is deleted, the disconnect functionality can be used. Once the refresh token is removed as part of the authentication flow the user will be asked to choose a new PIN.