PIN authenticator

The PIN authenticator is the default authenticator in the Onegini SDK. It is always available for a registered user. When authenticating with other authenticators there is always an option to fallback to PIN. The PIN authenticator therefore cannot be deregistered except by deregistering the user.

Pin handling recommendations

The Onegini SDK does its best in order to deliver a secure and seamless experience to the end-user. In order to do so, all data-sensitive operations require the end-user to be authenticated first to ensure that the data is secured, even on a stolen phone. The PIN plays a crucial role in this flow:

  • The SDK requires a PIN for any new end-user as default authentication method
  • The SDK / User may fallback to PIN authentication seamlessly if any other authentication mechanism fails (such as TouchID)

One of the aspects of the SDK internals is that the PIN is not stored neither in memory nor on the device. This approach secures the user's PIN from being intercepted. However it has some disadvantages from the developer's perspective: since the SDK doesn't store the PIN there is also no way to get it's required length during PIN entrance for the user authentication. The only source of truth in this case is the Admin Panel of the Token Server so how to deal with it?

In short there are two way: to store the PIN length or to hardcode it. While hardcoding is easy to achieve it has a downside such as an inability to dynamically change the PIN length in the Token Server configuration without updating your application. Storing the PIN length makes your app more flexible while it requires little changes to support it.

Lets imagine an app that uses the Onegini SDK. A Developer (we'll refer to him as Johan from now on) has implemented a PinInputViewController, that uses a pinLength property in order to build his UI and to validate the PIN length locally. Below you can see the sample of Johan's code that handles user authentication:

// id<ONGAuthenticationDelegate>

- (void)userClient:(ONGUserClient *)userClient didReceivePinChallenge:(ONGPinChallenge *)challenge
{
    PinInputViewController *inputViewController = [PinInputViewController new];
    inputViewController.pinLength = ?

    inputViewController.completion = ^(NSString *pin, BOOL cancelled) {
        if (cancelled) {
            [challenge.sender cancelChallenge:challenge];
        } else {
            [challenge.sender respondWithPin:pin challenge:challenge];
        }
    };
}

The code shown above is fairly simple. The only missing part is where we can grab the PIN length from. For this purpose we can design a class, that acts as a Dictionary by persisting the length for a specific user. We need to store the PIN length per user profile since the PIN policy could change at any moment in time and therefore different profiles could use different PIN lenghts. Also you should persist the length so that it does not get wiped after killing the app since you will need it the next time the app starts and authentication is triggered. Below is the interface description of the dictionary:

@interface PinLengthStore : NSObject

- (NSUInteger)lengthForUser:(ONGUserProfile *)userProfile;
- (void)setLength:(NSUInteger)length forUser:(ONGUserProfile *)userProfile;
- (void)removeLengthForUser:(ONGUserProfile *)userProfile;

@end

and update userClient:didReceivePinChallenge: correspondingly:

- (void)userClient:(ONGUserClient *)userClient didReceivePinChallenge:(ONGPinChallenge *)challenge
{
    PinInputViewController *inputViewController = [PinInputViewController new];
    inputViewController.pinLength = [self.pinLengthStore lengthForUser:challenge.user];

    // rest of the code 
}

Now Johan needs to implement the places where he must write the pinLength value. For storing / updating the PIN length there are only two places: registration and change PIN:

Registration

// Implementation of id<ONGRegistrationDelegate>

@property (nonatomic) NSUInteger userPinLength;

- (void)userClient:(ONGUserClient *)userClient didReceivePinRegistrationChallenge:(ONGCreatePinChallenge *)challenge
{
    self.userPinLength = challenge.pinLength;

    // further challenge handling
}

- (void)userClient:(ONGUserClient *)userClient didRegisterUser:(ONGUserProfile *)userProfile
{
    [self.pinLengthStore setLength:self.userPinLength forUser:userProfile];
}

Change Pin

// Implementation of id<ONGChangePinDelegate>

@property (nonatomic) NSUInteger userPinLength;

- (void)userClient:(ONGUserClient *)userClient didReceiveCreatePinChallenge:(ONGCreatePinChallenge *)challenge
{
    self.userPinLength = challenge.pinLength;

    // further challenge handling
}

- (void)userClient:(ONGUserClient *)userClient didChangePinForUser:(ONGUserProfile *)userProfile
{
    [self.pinLengthStore setLength:self.userPinLength forUser:userProfile];    
}

As you may notice, new pin length gets stored only when operation has been completed successfully. The reason of doing that is to prevent storing length for non-registered users or to change the length during change PIN when the PIN is not actually changed (and thus making it potentially invalid).

The final thing Johan has to do is remove the stored value in every delegate method where the user may get deregistered:

// id<ONGAuthenticationDelegate>

- (void)userClient:(ONGUserClient *)userClient didFailToAuthenticateUser:(ONGUserProfile *)userProfile error:(NSError *)error
{
    if ([error.domain isEqualToString:ONGGenericErrorDomain] && error.code == ONGGenericErrorUserDeregistered) {
        [self.pinLengthStore removePinLengthForUser:userProfile];

        // further logout cleanup
    }
}