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.

system architecture

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.2.379'){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_USER - the default role assigned once a user has authenticated against an entity but is not certified

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

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

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

  • ROLE_USER_CERTIFIED - 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_CERTIFIED")){
        Log.i("TAG", "You have ROLE_USER_CERTIFIED, you are certified and can call further API's");
    } else {
        Log.i("TAG", "You do not have the correct role to perform 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 Process

In order for a user to be able to authenticate online and sign documents, they need to have first completed the "certification process". This process involves three steps: Phone Verification, Registration and Certification.

Phone 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

To register an account for the selected entity, the user needs to fill in a registration form. The identity fields for this entity can be retrieved and populated as follows:

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

For a user to become "certified", a Digital Certificate should be issued by a recognised Certification Authority (CA), and a "private key" should be generated and securely stored in the Secure Enclave of the device.

A registered user on a verified device will have a status of UNACCREDITED.

A user can be certified in different ways, depending on the "Accreditation Level" required for certification.

The accreditation process is the process of verifying the user identity, either online or in person, to enable the CA to issue a Digital Certificate for the user.

The biid Platform supports 3 levels of accreditations (Level 1 to Level 3). For each level there are different legal and procedural requirements, needed to verify the user identity.

Different accreditation levels can be defined for different entities and an entity can support multiple accreditation levels, with each level replacing the previous one.

Once an accreditation process is successfully completed, the user will have the status UNCERTIFIED. Once accredited biid Platform will generate a 16 digit code for the user. This code should be kept safe and secret, in order to protect the user account. This code is known as a "DIAC", which stands for "Digital Identity Activation Code".

Receipt of a DIAC, either from an API or via email, etc., is a prerequisite for completing the certification process, as a Digital Certificates can only be generated when providing a valid DIAC for the specific accreditation level.

The following paragraphs detail the requirements and processes a user needs to follow in order to get accredited and certified to a specific level.

Accreditation Level L1

An accreditation Level 1 allows a user to "self accredit" i.e. registering their details online without the need of an operator reviewing the provided information.

Once the Level 1 accreditation process is complete, the user will be issued a "self signed" Digital Certificate issue from the biid platform directly (not from external Certificate Authority).

This type of certificate does not provide any legal validity when signing documents but it can still be used to authenticate online and authorise transactions.

If a user has a status of UNCERTIFIED and an accreditation Level 1, the biid platform exposes an API to "auto-certify" the user. In this case the user does not need to perform any manual action such as typing their Level 1 DIAC into a certification screen.

Certifying a user for accreditation Level 1 can be done as follows:

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

The app can request a certificate without user interaction:

try {
    Sdk.getClient().certifyUserAtLevel1(this);
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (UserNotAccreditedException e) {
    e.printStackTrace();
} catch (DeviceNotVerifiedException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

Level 2 and Level 3 Certification Process

Level 2 and Level 3 accreditation may require the user to provide additional information and proof of identity. An operator of the entity will then have to review and validate this information, before the user can be accredited and a DIAC generated.

The amount of details and documentation required to accredit a user Level 2 or Level 3 may differ substantially, but in general the accreditation process depends on the legal framework the generated biid identity needs to comply with.

For instance, if a "Qualified Digital Certificate", according to the European eIDAS Regulation need to be issued, the accreditation process will require the user provides a copy of a valid ID document (e.g. a Passport or national ID card) as well as undertaking an identity verification process that might include biometric verification, face analysis and even a visit to a local checkpoint, to meet an operator in person.

The required accreditation process is always agreed between the organisation that provides the biid service and the Certification Authority that will need to issue Digital Certificates to end users.

  • Level 2 accreditation (known as "Low" in the eIDAS Regulation) does not require the user to visit a checkpoint or undertake a video call in order to verify their identity, it does however require that an operator approves the user accreditation request.

  • Level 3 accreditation (known as "Substantial" in the eIDAS Regulation) requires that the user either visit in person an authorised checkpoint, undertake a video call with an operator or use a recognised ID verification tool, in order to formally verify their identity.

Full details on the eIDAS Regulation can be found here.

Locations at which a user can be accredited can be retrieved using the Checkpoint API: javadoc

Once the accreditation process has been completed and the DIAC received, the user can certify by entering the DIAC with the certifyUser API, as follows:

try {
    Sdk.getClient().certifyUser(this, "3924677809504936".toCharArray());
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (UserNotAccreditedException e) {
    e.printStackTrace();
} catch (ValidationException e) {
    e.printStackTrace();
} catch (DeviceNotVerifiedException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

In the background the certifyUser process is made up of the following steps:

  1. An asymmetric key pair is created on the device.

  2. A signing request (CSR) is generated, signed with the private key and sent along with the DIAC, as part of the API request.

  3. If the DIAC and CSR are valid, the CSR is sent off to the appropriate Certification Authority. The certifyRequest will then return a success response and the user status will be set to CERTIFICATE_PENDING.

  4. If the CA issues a signed digital certificate, the next call to requestUser will show the status CERTIFIED. The signed certificate is then retrieved and securely stored on the device.

If requestUser is called immediately after a request for certification and the user is successfully certified, then it is likely that the request will return an InvalidTokenError error as the cached token will no longer be valid. This can be resolved by initiating the action detailed in the resolution property of the error. See javadoc for further details.

See InvalidTokenException for general token exceptions.

Level 2 and Level 3 Certification Flow

user certification L2 L3

No matter the accreditation level, once successfully certified the user will be able to authenticate online, authorise transactions and sign documents.

The legal validity of the digital signature will instead depend on the accreditation level of the user, with Level 3 providing the highest level of validity.

Uncertify

In certain circumstances the user may want to uncertify for the current selected entity on the device.

This can be done using the uncertify() API.

try {
    Sdk.getClient().uncertify();
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

After successfully calling uncertify(), subsequent calls to other APIs will result the return of an InvalidTokenException as the cached token will no longer be valid. This can be resolved by initiating the action detailed in the resolution property of the error. See javadoc for further details.

At this point the user’s status for this entity will be UNCERTIFIED.

See InvalidTokenException for general token exceptions.

Transaction Handling

A certified user can manage the following three types of transactions:

  • Authentication

  • Documents

  • Message

===Transaction Actions

Each transaction type has a sepcific set of actions that can be applied to it. These are detailed below:

Transaction Type Confirm Sign Reject Dismiss

Authentication

Document

Message

Requesting Transactions

The requestTransactions() can be used to request a paged and filtered list of transactions. The results can be filtered on Status (by specifying transactions with which statuses to return) and the Type of transaction to return.

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

Authentication requests

An authentication request can be confirmed by first requesting the Transaction‘s details by it’s ID and then confirming it using confirmAuthentication(). On successful confirmation, the evidence generated is stored in the Back Office with the transaction.

AuthenticationTransaction transaction = requestAuthenticationTransaction(id);

client.confirmAuthentication(transaction);

Or reject the request like so:

client.rejectAuthentication(id);

Document signing requests

Retrieving document details

The details of a Document in a document transaction request by requesting the document’s ID as follows:

try {
    Document document =  Sdk.getClient().requestDocument("id");
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

Document Image preview

To retrieve a preview image of a document, the document object should first be retrieved as above and then passed in as a parameter to the following request:

try {
    File file = Sdk.getClient().requestDocumentPreview(document);
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

Document content

To download the content of a document to the device, the document object should first be retrieved as above and then passed in as a parameter to the following request:

try {
    ParcelFileDescriptor descriptor = Sdk.getClient().requestDocumentContent(document);
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

Publicly accessible documents

If a document has been marked as isPublic on the server side, it is possible to retrieve the document content, for display or download, via it’s getPublicUrl.

Signing a document (Level 1, 2, 3 accredited user)

A user accredited to Level 1,2 or 3 can sign the documents in a transaction as follows:

 try {
    DocumentsTransaction transaction = Sdk.getClient().requestDocumentsTransaction("id");
    Sdk.getClient().signDocuments(transaction);

} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
} catch (TransactionNotPendingException e) {
    e.printStackTrace();
} catch (InvalidCredentialsException e) {
    e.printStackTrace();
}

A document signing request can be rejected as follows:

try {
    DocumentsTransaction transaction = Sdk.getClient().requestDocumentsTransaction("id");
    Sdk.getClient().rejectDocuments(transaction.getId());

} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
} catch (TransactionNotPendingException e) {
    e.printStackTrace();
}

Messages

A Message transaction is an information based transaction. It has only one action and that is Dismiss. Dismissing a Message Transaction, generates evidence providing proof that the message has been "seen".

A message can be dismissed by first requesting the message Transaction’s details by it’s ID and then dismissing it using dismissMessage() :

try {
    Sdk.getClient().dismissMessage(messageTransaction);
} catch (NoConnectionException e) {
    e.printStackTrace();
} catch (InvalidTokenException e) {
    e.printStackTrace();
} catch (TransactionNotPendingException e) {
    e.printStackTrace();
} catch (ClientException e) {
    e.printStackTrace();
}

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

Successful 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)

Certificate Pinning

The biid SDK implements an additional level of security through the use of Certificate Pinning (SSL Pinning). Certificate pinning ensures that the SSL certificate returned from an SSL connection exactly matches a hash of the certificate which is stored within the manifest.

The manifest which is generated contains the certificate hash (PIN), there is no need to manually import or add anything.

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 language settings, require a login or re-authentication before they will be applied to messages returned from the server.

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();
}

Additonal Code Samples

User calls

Extend Permissions

Extends Permissions allows a certified user to see their transactions on a secondary device without having to have certified on that device itself.

try {

    Sdk.getClient().requestExtendPermissions("3924677809504936".toCharArray());

} catch (NoConnectionException e) {
    e.printStackTrace();
    // no connrection
} catch (InvalidTokenException e) {
    // token expired, reauthentication could be needed.
    e.printStackTrace();
} catch (UserNotAccreditedException e) {
    // user has to be accredited and certified on at least one other device
    e.printStackTrace();
} catch (ValidationException e) {
    e.printStackTrace();
    // incorrect DIAC
} catch (DIACLimitException e) {
    e.printStackTrace();
    // number of requests with DIAC exceeded
} catch (DeviceNotVerifiedException e) {
    e.printStackTrace();
    // device is not verified
} catch (ClientException e) {
    e.printStackTrace();
    // The request failed
}