Introduction

The biid Core SDK is the easiest way to use the biid Platform in an Android app. The biid Core SDK library contains a biid Client. This client handles communication with the biid Platform and manages the biid digital certificates.

infrastructure

The developers guide describes how to integrate the biid Core SDK library and use the biid Client. For further details please check the javadoc.

Integration

Android Studio

Include the biid Core SDK library as a dependency in your gradle project:

repositories {

    ...

    maven {
        url "http://nexus.stage-biid.com/content/repositories/client-releases"
    }

    ...
}

dependencies {

    ...

    compile ('com.biid:sdk-core-android:3.1.322'){transitive=true}

    ...
}

Supported Android API levels

The biid Core SDK library supports Android API level 21 and higher.

Manifest

biid will provide you with a manifest file. Place this file in the assets directory of your Android project.

Referencing the biid Client

The biid client, and it’s APIs and properties, is accessible through the SDK’s getClient() function:

Client client = Sdk.getClient();

Initialization

The SDK can be initialized as follows (and should be the first call to the client):

client.initialize(context);

Roles

Once a user is authenticated, a role or roles is assigned to that user. Roles are used to control access to APIs. If the user is not assigned a role required for a particular API, in general a ClientException with be returned with an error message of Access is denied.

The roles assigned to a user are determined by a number of factors including the user’s status, their currently selected entity etc.

The available roles are:

  • ROLE_GUEST - the default role assigned once a user has authenticated against an entity but is not certified

  • ROLE_GUEST_EXTENDED - an extended guest role available to certified users on a secondary device

  • ROLE_GUEST_LIMITED - a limited guest role assigned when a user is authenticated but not associated with an entity

  • ROLE_LOCKED - role assigned to an authenticated user who is has a user status of "locked"

  • ROLE_USER - the role assigned to a user authenticated against an entity and who is also certified on the new device

To get the roles for the currently authenticated user, use the getRoles() API :

List<String> roles = null;
try {
    roles = Sdk.getClient().getRoles();

    if(roles.contains("ROLE_USER")){
        Log.i("TAG", "You have ROLE_USER, you are certified and can call further API's");
    } else {
        Log.i("TAG", "You do not have the correct role to preform this action.");
    }

} catch (ClientException e) {
    e.printStackTrace();
}

Device InstallationID

A unique installationID for a device is required and is automatically generated for each new app installation. The current installationID can be returned as follows:

try {
    Log.i("TAG", "Your installation ID: " + Sdk.getClient().getInstallationId());
} catch (ClientException e) {
    e.printStackTrace();
}

On occasions it may be appropriate for the installationID to be set to a required value. This can be done as follows:

try {
    final String installationId = UUID.randomUUID().toString();

    Sdk.getClient().setInstallationId(installationId);

} catch (ClientException e) {
    e.printStackTrace();
}

Language Settings

See Localization section for details on how to return and set the language code used for localised content.

Logging in

After the client is initialized, you must authenticate the user with a username and access token from the security provider.

This selects the entity that is passed in, or if no entity is passed in, the entity linked to the app.

client.authenticate(username, accessToken, entity);

If any call fails with an InvalidTokenException, check the resolution in the exception:

if (exception.getResolution() == InvalidTokenException.Resolution.LOGIN) {
    // get a new access token by authenticating again
if (exception.getResolution() == InvalidTokenException.Resolution.RETRY) {
    // retry the current call one more time.
} else {
    // no resolution, treat this as an error.
}

Check which entity is selected with:

client.getSelectedEntity()

Multiple entities

The following is only relevant for apps that support multiple entities.

To get a list of entities the user is linked to, use:

List<Entity> entities = client.requestEntitiesForUser();

Or, if the user wants to add another entity, get an entity by url name with:

Entity entity = client.requestEntity(urlName);

Use this call to select another entity:

client.selectEntity(entity);

When you make any other call to the client, the selected entity will be implied.

In certain integrations, for example when only one entity is available and the entity details are known to the SDK integrator, it makes sense to create the entity model to pass into selectEntity() rather than retrieving it via a checkEntityURl() call. This can be done by creating an Entity object with it’s ID:

	final Entity entity = new Entity("2995046156774445007121104"); // constructor parameter is the Entity Id.
try {
    Sdk.getClient().selectEntity(entity);
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

In addtion there is also a helper API that creates and selects the Entity in one call:

try {
    Sdk.getClient().selectEntity("2995046156774445007121104");
    // the parameter is the Entity Id.
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

Certification

For a user to be able to authenticate or sign documents they need to have a digital certificate on the device. This requires three steps.

Verification

The device the app runs on needs to be verified before the user can be certified. Even if the app supports multiple entities, the device only needs to be verified once.

Before the device is verified, the user has status UNVERIFIED.

First the user requests an verification code:

client.requestUserDeviceVerification();

An SMS with the verification code will be send to the phone number the user registered with. When the user receives the code, they can verify the device:

client.verifyUserDevice(code);

Registration

After verification the user will have status UNREGISTERED:

client.requestUser().getStatus() == User.Status.UNREGISTERED

The user needs to fill in a registration form.

List<FieldDefinition> fields = client.requestEntityDetails().getUserFieldDefinitions();

// user enters a value for each field

Which will be send off like this:

User user = new User();
for (FieldDefinition field : fields) {
   user.put(field.getName(), value)
}

client.registerUser(user);

Note that if field.isReadOnly() is true, changes in the value of the field will be ignored.

Certification

A registered user on a verified device will have status UNCERTIFIED.

Accreditation Level L1

If an entity is configured to support level L1, a registered user automatically becomes accredited at level L1.

user.getAccreditationLevel() == User.AccreditationLevel.L1

The app can request a certificate without user interaction:

client.certifyUserAtLevelL1(context);

Accreditation Level L2

Accreditation levels above L1 require a formal accreditation process, which culminates in an operator accrediting the user.

If the user already has an certificate, that certificate will be revoked.

A Digital Identity Access Code (DIAC) will be send to the user by SMS or email. After the user enters the DIAC, the app requests a certificate with:

client.certifyUser(context, diac);

Accreditation Level L3

Accreditation level L3 requires that the user visits a checkpoint to get accredited.

List<Checkpoint> checkpoints = client.requestCheckpoints(max, offset, location, order);

The DIAC will be send by post. After the user enters the DIAC, the app requests a certifcate with:

client.certifyUser(context, diac);

Requesting a digital certificate

Both certify requests create a digital certificate on the device. A signing request for this certificate is sent off to a Certificate Authority (CA). The user is now in status CERTIFICATE_PENDING.

After the certificate has been signed by the CA the user will be in status CERTIFIED and can authenticate and sign documents.

Authenticating and signing

A certified user can handle two kinds of transactions: authentication requests and document signing requests. To get a paged and filtered list of transactions, use:

List<Transaction> transactions = client.requestTransactions(offset, max, statuses, type, documentType);

Authentication requests

A user may confirm an authentication request like this:

AuthenticationTransaction transaction = requestAuthenticationTransaction(id);

client.confirmAuthentication(transaction);

Or reject the request like so:

client.rejectAuthentication(id);

Document signing requests

To get the document to be signed, use:

ParcelFileDescriptor documentFile = client.requestDocumentContent(document);

A user may sign the documents in a transaction like this:

DocumentsTransaction transaction = requestDocumentsTransaction(id);

client.signDocuments(transaction);

Or reject the request like so:

client.rejectDocuments(id);

Push messages

The biid Platform can send a push message through the Firebase Cloud Messaging service when the user’s certificate has been signed or when a transaction is created.

To enable this, integrate Firebase Cloud Messaging in your app and enter the (long) server key in the biid back-office.

Extend the FirebaseInstanceIdService to pass the token to the biid Client:

public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {

    @Override
    public void onTokenRefresh() {

        ...

        String token = FirebaseInstanceId.getInstance().getToken();
        client.onFcmTokenRefreshed(token);

        ...
    }
}

And extend the FirebaseMessagingService to handle the messages:

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    @Override
    public void onMessageReceived(RemoteMessage message) {

        ...

        biidNotification notification =
            client.onFcmMessageReceived(message.getFrom(), message.getData());

        if (notification == null) {
            // the push message is not from the biid Platform
        } else {
            // notify the user
        }

        ...
    }
}

SMS messages

The biid Platform can also send an SMS message with a link when the user’s certificate has been signed or when a transaction is created.

To enable this, enter an app identifier in the biid back-office.

Add the following intent-filter to the activity in which you want to handle the transaction notification:

<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="http" android:host="[your app identifier]" />
</intent-filter>

And handle the intent like this:

biidNotification notification = client.onSmsReceived(intent);

if (notification == null) {
    // the SMS is not from the biid Platform
} else {
    // notify the user
}

Exception Handling

Sucessful requests made to the Client APIs are returned from the method itself. Failures from the APIs are flagged using exceptions. This section details some of the important and generic exceptions that are thrown from various APIs.

Some generic exceptions that are thrown are the following:

<<NoConnectionException>>
<<ClientException>>
<<InvalidTokenException>>
<<ValidationError>>

Some other APIs additionally throw exceptions specific to their functionality, for example registerUser throws UserAlreadyRegisteredExeption.

NoConnectionException

A NoConnectionException is thrown if a network request fails. Reasons for failure can include:

No active network available
No matching certificate found for the pinned endpoint (see Certificate Pinning).

ClientException

ClientExceptions are thrown when an error occurs with a biid client method or a known error code from the backend services. ClientExceptions contain a number of methods to help identity the error:

  • getMessage() : An a string providing additional information where relevant

  • getTraceID() : unique ID generated per API call. Can be used for error tracking

InvalidTokenException

If a call fails with an invalid token, it means that the user’s authentication has expired. It is possible to check the error’s Action property to determine what action should be taken to resolve the error:

try {
    Sdk.getClient().requestUser();
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException invalidTokenExeption) {
    if (invalidTokenExeption.getResolution() != InvalidTokenException.Resolution.LOGIN) {
        //reauthenticate(...)
    }
} catch (ClientException e) {
    e.printStackTrace();
}

ValidationException

APIs which take user input that is incorrectly entered throw ValidatonExceptions. This exception can contain multiple errors within it. For example when registering a user multiple fields could be missing or incorrect. The ValidationException will contain an map of errors which are relevant to the call. The Map key will be the field Id which is invalid. This is usefull during registration.

private void doRegistration(final User user) {

    try {

        Sdk.getClient().registerUser(user);

    } catch (ClientException e) {
        e.printStackTrace();
    } catch (UserAlreadyRegisteredException e) {
        e.printStackTrace();
    } catch (NoConnectionException e) {
        e.printStackTrace();
    } catch (InvalidTokenException e) {
        e.printStackTrace();
    } catch (ValidationException validationException) {
        final Map<String, String> registrationErros = validationException.getErrors();

        for(String key : registrationErros.keySet()) {
            Log.e("TAG", "Field " + key + " : " + registrationErros.get(key));
        }

        // Also you will see all the validation errors when calling:

        Log.i("TAG", "ValidationException: " + validationException.getMessage());

    }
}

TraceID

Each API request generates a unique ID (called a trace_ID) which is then associated with the request. This allows the request to be tracked though the back end micro-services. This id has been exposed via the getTraceId() method in all biidExceptions as an aid to debugging any issues with the call i.e. it can be used to find the request in the back end error logs. This id can also be used to uniquely identify customer errors in production enviroments, the trace ID may want to be surfaced in a log or in the UI to the customer in some situations.

The traceID can be accessed as followed:

try {
    final User user = Sdk.getClient().requestUser();

} catch (NoConnectionException e) {
    // bad connection
} catch (InvalidTokenException e) {

    Log.e("TAG", "Invalid Token, TraceID: " + e.getTraceId(), e);
    e.printStackTrace();
} catch (ClientException e) {

    Log.e("TAG", "Client exception: "+ e.getMessage());
    // e.getMessage() will print a trace ID if relevant.

    e.printStackTrace();
}

Localization

The following APIs are available for managing the language localization. Any changes to an entity’s lanugage settings, require a login or re-authentication before they will be applied.

getLanguage() returns the current language code (ISO 639-1) used for localised content:

try {
    String language = Sdk.getClient().getLanguage();
} catch (ClientException e) {
    e.printStackTrace();
}

setLanguage() overrides the device’s language for returned localised content:

try {
    Sdk.getClient().setLanguage("en");
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}