User authentication

Introduction

The Onegini SDK uses the oAuth 2.0 protocol to authorize the device to access protected resources. To support this protocol the SDK acts as an oAuth 2.0 client.

Connecting a device

Flow details

  1. APP → SDK: The app initiates authentication on the SDK and provides the required scopes and the handlers to handle the result.
  2. SDK → Token Server: The SDK initializes DCR
  3. Token Server → SDK: The Token Server generates an OCRA challenge.
  4. SDK → Token Server: The SDK creates an OCRA response based on the received challenge.
  5. Token Server → SDK: When the correct OCRA response is received a new set or client credentials is generated and returned.
  6. SDK → Browser: The SDK redirects to the browser to start to perform an authorization request on the token server.
  7. Browser → Token Server: The Token Server authorization endpoint is called.
  8. Token Server → Browser: Redirect to IdP for authentication.
  9. Browser → IdP: Authenticate
  10. IdP → Browser: Redirect to Token Server with authentication result.
  11. Browser → Token Server: Authorize
  12. Token Server → Browser: An authorization grant is being generated after consent was given.
  13. Browser → APP: Redirect back to the app via the configured scheme.
  14. APP → SDK: Delegate the handling of the incoming redirection to the SDK.
  15. SDK → Token Server: Request an access token for the received access grant.
  16. Token Server → SDK: The generated access token with optionally a refresh token is returned to the SDK.

Initializing authentication

To initialize authentication client credentials are required. These credentials are received via Dynamic Client Registration (DCR). As an app developer there is no need to initialize DCR as it is part of the default implementations in the SDK. Errors can occur during DCR, these errors are reported back to the SDK via the specified handler. For DCR the app needs to identify itself to the Token Server. This is done by sending the identifier, version and platform of the app. For the combination of these parameters a signature should be defined on the Token Server. A signature of the app is used which is unique for this particular application and its configuration. To generate an application signature please see the application signature chapter. The signature will be verified via the OCRA protocol. As a timestamp is used within the OCRA protocol it is mandatory that the time on the device is equal to the time on the Token Server, independent of time zones. The maximum time offset is ten minutes. There are two common reasons why DCR would fail. First the time on the device not being in sync and second the app version not properly configured in the Token Server. The SDK does not provide the app with an error other then a general DCR error.

The UserClient exposes two main methods for authentication: UserClient#registerUser() and UserClient#authenticateUser() to support multiple user profiles for the same mobile client. A list (Set<UserProfile>) of registered user profiles can be checked by calling UserClient#getUserProfiles(). When the app is being used for the first time (and the list of profiles is empty), or when user wants to create a new profile, then the registerUser() method should be used. For next authentication the user should be authenticated with authenticateUser() method, that takes specific UserProfile value object as a parameter. In both cases, when user is authenticated, the SDK will call onSuccess(UserProfile userProfile) methods from respectivly OneginiAuthenticationHandler for UserClient#authenticateUser() or OneginiRegistrationHandler for UserClient#registerUser() to return authenticated UserProfile value object. The same value object of currently authenticated user can be accessed via UserClient#getAuthenticatedUserProfile() method.

The result of authentication is an access token with optionally a refresh token. When a refresh token is available in the authentication flow the SDK will use the refresh token to authenticate. As the refresh token is stored encrypted on the device, the user has to provide his PIN (or a fingerprint) via the specified authentication request handler to decrypt it. In case of the fingerprint authentication, the android system is responsible for recognizing the correct fingerprint - the refresh token will not be accessible when wrong fingerprint will be provided. In case of the PIN authentication, the decrypted refresh token will be send to the Token Server to validate the users pin. When the wrong pin was entered for too many times the SDK will remove the stored refresh token. If the device was used for mobile authentication this won't be possible anymore from this point.

When authenticating for the first time, or when refresh token of particular user profile was revoked, the SDK will instruct the app to redirect the user to the authorization endpoint on the Token Server via the specified browser. When the client credentials are invalid the Token Server is not able to redirect the user back to the app. As this will potentially make the app instance useless unless re-installing the app. The SDK will validate the client credentials and refresh them before redirecting to the token server to receive an access grant.

Example code - registration of a new user profile

OneginiSDK.getOneginiClient(this).getUserClient().registerUser(userProfile, new OneginiRegistrationHandler() {
    @Override
    public void onSuccess(final UserProfile userProfile) {
      // show user is registered and authenticated
    }

    @Override
    public void onError(final OneginiRegistrationError error) {
      // check error reason and handle it or explain it to the user
    }
  }
}

Example code - authentication of already created user profile

OneginiSDK.getOneginiClient(this).getUserClient().authenticateUser(userProfile, new OneginiAuthenticationHandler() {
    @Override
    public void onSuccess(final UserProfile userProfile) {
      // show user is authenticated
    }

    @Override
    public void onError(final OneginiAuthenticationError error) {
      // check error reason and handle it or explain it to the user
    }
  }
}

Processing the registration

A result of the registration initialization can be a request for an access grant to the Token Server via the browser. In such case the SDK will notify the app via OneginiRegistrationRequestHandler#startRegistration() method call. The app should use provided url to for ask for the access grant in a separate web browser.

Example opening external browser

  @Override
  public void startRegistration(final Uri uri, final OneginiRegistrationCallback oneginiRegistrationCallback) {
    // we can store the callback in order to pass the registration result later on
    CALLBACK = oneginiRegistrationCallback;

    final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

    context.startActivity(intent);
  }

When user gets his access granted, the Token Server will redirect back to the app. As the app is a layer on top of the SDK, the SDK can not handle this redirect itself. It is the responsibility of the app to handle the redirect and delegate it to the SDK. When delegating a redirect to the SDK the SDK will verify if the redirect has the right syntax and if it should be handled by the SDK. If the SDK decides the redirect should be handled it will continue to process it. The app can handle a redirect by specifying a scheme in the AndroidManifest.xml of the application. Please make sure that you set the same scheme in OneginiConfigModel and in Token Server's configuration

Example AndroidManifest.xml

<application
 ...
  <activity
      android:name=".view.activity.LoginActivity"
      android:launchMode="singleTask">
    <intent-filter>
      <action android:name="android.intent.action.VIEW"/>
      <category android:name="android.intent.category.DEFAULT"/>
      <category android:name="android.intent.category.BROWSABLE"/>

      <data android:scheme="oneginiexample"/>
    </intent-filter>
  </activity>
 ...
</application>

Processing a callback includes validation of the callback itself. Errors reported in the callback will be reported to the app via the handler. For a successful callback the SDK will start using the included authorization grant. Based on this authorization grant an access token will be requested for the specified set of scopes. When the client has the refresh token grant type a refresh token will be returned with the created access token for a correct access grant by the Token Server. Once a refresh token is received the SDK will initialize a PIN authentication request to get the users PIN. The PIN is used to store the refresh token encrypted on the device. Depending on the setting chosen by the app developer the user should confirm the entered PIN or not. The PIN authentication request handler can be specified by the app developer by setting it on the OneginiClientBuilder. The received access token will not be persisted and only stored in memory, so it will be gone the next time the app is started.

Example code to handle registration callback

  @Override
  public void onNewIntent(final Intent intent) {
    super.onNewIntent(intent);
    handleRedirection(intent.getData());
  }

  private void handleRedirection(final Uri uri) {
    if (uri == null) {
      return;
    }

    final OneginiClient client = OneginiSDK.getOneginiClient(getApplicationContext());
    final String redirectUri = client.getConfigModel().getRedirectUri();
    if (redirectUri.startsWith(uri.getScheme())) {
      // here we can call the OneginiRegistrationCallback stored in previous step
      if (CALLBACK != null) {
        CALLBACK.handleRegistrationCallback(uri);
        CALLBACK = null;
      }
    }
  }

When calling the OneginiRegistrationCallback#handleRegistrationCallback method there is no need to specify a handler, because the OneginiRegistrationHandler handler from the registration callis used instead. In case when the registration in web browser has failed or was aborted by the user, you can call OneginiRegistrationCallback#denyRegistration() method in order to abort registration process.

Logout a user

In the SDK a user is treated as logged in as long as the user has an access token for the operation to be executed. To logout the user the access token should be removed. This can be done by calling the logout function on the UserClient. The SDK will also send a request to the Token Server to remove the access token to ensure the token is invalidated both on the client as server side. If a refresh token is available this will still be available after the logout action.

Example code to logout a user

OneginiClient.getInstance().getUserClient().logout(

  new OneginiLogoutHandler() {
   @Override
   public void onSuccess() {
     // Go to home screen
   }

   @Override
   public void onError(final LogoutError error) {
     // Handle the error or simply ignore and return to home screen
   }
 }
);

Deregister user profile

Deregistering a user profile implies the removal of all his data (including access and refresh tokens) from the device. The SDK will also send a request to the Token Server to revoke all profile's tokens. The client credentials and other user profiles will remain stored on the device.

Example code to deregister a user profile

OneginiClient.getInstance().getUserClient().deregisterUser(userProfile, new OneginiDeregisterUserProfileHandler() {
    @Override
    public void onSuccess() {
        // Go to home screen
    }

    @Override
    public void onError(OneginiDeregistrationError error) {
        // Handle the error or simply ignore and return to home screen
    }
 }
);

Schematics authentication with PIN

title Authentication with PIN

User-->App: Startup application
App->SDK: OneginiClientBuilder(android.content.Context, OneginiCreatePinRequestHandler, OneginiPinAuthenticationRequestHandler).build()
App->SDK: OneginiClient.getInstance().getUserProfiles()
SDK->App: Return registered user profiles list
note over User, App: The app can show a list of registered profiles\nwith an option to login with existing profile or\ncreate a new one
alt register new user profile
    User-->App: Register new profile
    App->SDK:OneginiClient.getInstance().registerUser(OAUTH_SCOPES, OneginiAuthenticationHandler)
    note right of App: OAUTH_SCOPES is a string array with scopes to which the user should get access
    SDK->Browser: Get authorization grant
    note over SDK, Browser: Login module
    Browser-->App: Return authorization grant
    App->SDK: OneginiClient.getInstance().handleRegistrationCallback(Uri)
    note right of App: For example: \n\private void handleRedirection(final Uri uri) {\n final OneginiClient client = OneginiSDK.getOneginiClient(getApplicationContext());\n if (uri != null && client.getConfigModel().getRedirectUri().startsWith(uri.getScheme())){\n client.getUserClient().handleRegistrationCallback(uri);\n }\n}\n
    SDK->App: OneginiCreatePinRequestHandler.startPinCreation(UserProfile, OneginiPinCallback, int)
    App->SDK: OneginiPinCallback.acceptAuthenticationRequest(char[])
    opt
        loop
            SDK->App: OneginiCreatePinRequestHandler.onNextPinCreationAttempt(OneginiPinValidationError)
            App->SDK: OneginiPinCallback.acceptAuthenticationRequest(char[])
        end
    end
    SDK->App: OneginiCreatePinRequestHandler.finishPinCreation()
    alt pin created
        SDK->App: OneginiAuthenticationHandler.onSuccess(UserProfile)
        App-->User: User is logged in
    else pin creation canceled
        SDK->App: OneginiAuthenticationHandler.onError(OneginiAuthenticationError)
        App-->User: User registration failed
    end
else authenticate existing user profile
    User-->App: Login with existing profile
    App->SDK: OneginiClient.getInstance().authenticateUser(UserProfile, OneginiAuthenticationHandler)
    SDK->App: OneginiPinAuthenticationRequestHandler.startAuthentication(UserProfile, OneginiPinCallback, AuthenticationAttemptCounter)
    App->SDK: OneginiPinCallback.acceptAuthenticationRequest(char[])
    opt
        loop maximum failed pin attepts
            SDK->App: OneginiPinAuthenticationRequestHandler.onNextAuthenticationAttempt(AuthenticationAttemptCounter)
            App->SDK: OneginiPinCallback.acceptAuthenticationRequest(char[])
        end
    end
    alt valid pin provided
        SDK->App: OneginiAuthenticationHandler.onSuccess(UserProfile)
        App-->User: User is logged in
    else authentication error
        SDK->App: OneginiAuthenticationHandler.onError(OneginiAuthenticationError)
        App-->User: User is not logged in
    end
end

Forgot pin

The refresh token is stored encrypted on the device. For encryption of the token a PIN is used as an encryption key. When a user forgot his PIN the existing refresh token used to log in can't be decrypted and is useless in that sense. After few wrong PIN attempts (configurable on Token Server) the refresh token is removed automatically in both the SDK as on the server side. To provide the user an option to skip this three attempts before the refresh token is gone the deregister user profile functionality can be used. Once the refresh token is removed the user needs to re-authenticate and has to choice a new pin.