Skip to content

onelogin/onelogin-mfa-android

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OneLogin MFA SDK for Android

The OneLogin MFA SDK is a Android library for registering, displaying, and using Time Based One Time Passwords (TOTP), which are commonly used for two factor authentication. A TOTP is commonly referred to below as a Factor. The framework supports all factors that adhere to the standard defined in RFC 6238. In addition, OneLogin's own factor type, which is normally used in the Protect application, is also supported. OneLogin's factor type also extends TOTP to include additional security and functional aspects (configurable by the OneLogin administrator), such as rooted device detection and keyguard requirements.

Table of Contents

Installation

To install the library add the following dependency to the build.gradle file of your module:

repositories {
    mavenCentral()
    maven { url "https://jitpack.io" }
}
implementation("com.onelogin:onelogin-mfa-android-sdk:1.0.2")

Configuration

If you choose to utilize our provided QR Scanner, the camera permission must be added to your AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA"/>

Initialization

In order to use MFA client, the library needs to be initialized first, which would normally happen on the onCreate() method of your application.

The first step is to create a MfaConfiguration instance using the builder:

// Kotlin
OneLoginMfa.initialize(
    this,
    MfaConfiguration.Builder().isDebug(true).build()
)

// Java
MfaConfiguration mfaConfig = new MfaConfiguration.Builder().isDebug(true).build();
OneLoginMfa.initialize(this, mfaConfig);

Setting isDebug() to true will enable Timber logging. Default is false.

This initialization only needs to occur one time. After this, an instance of the MfaClient can be retrieved by calling OneLoginMfa.getClient(). The client is a singleton that can be used to perform any of the supported MfaClient operations.

SDK Overview

The MfaClient contains all of the methods which can be used to perform the supported MFA operations.

Factor

Both OneLogin and third party factors are represented by the class Factor. The model class is:

data class Factor(
    var id: Long, // ID generated by Room
    var credentialId: String?, // ID generated by OneLogin
    var subdomain: String?, // Subdomain that factor is registered to
    var shard: String?, 
    var username: String?, // Account associated with factor
    var seed: String, // Seed used to generate TOTP
    var issuer: String?, // Issuer of the factor
    var creationDate: Long, // Date factor was added
    var allowRoot: Boolean, // Device setting from OneLogin to allow rooted devices, defaults true
    var forceLock: Boolean, // Device setting from OneLogin to require keyguard, defaults false
    var paired: Boolean, // Device setting from OneLogin to determine if factor is still paired to account
    var displayName: String?, 
    var orderPreference: Int, // Order preference for sorting of factors
    var crypto: String, // Defaults to HmacSHA1
    var period: Int, // TOTP validity period
    var digits: Int, // TOTP digits
    // Not yet supported
    var allowBackup: Boolean, // Device setting from OneLogin to allow for backup, defaults to true
    // Not yet supported
    var requireBiometrics: Boolean // Device setting to require biometric verification to view OTP, defaults to false
)

Callback

The MfaCallback interface will be used to provide the results for most of the operations, this interface has two methods that should be overridden to process the results:

  • onSuccess(T: Success) This method will be called when the operation completes successfully and will contain the relevant information of the result.

  • onError(T: Error) This method will be called when there is an error on the operation and will return a Throwable with the information related to the error.

OneLoginMfaException

For all exceptions that occur within the OneLoginMfa library, a OneLoginMfaException will be thrown. The custom exception class is as follows:

data class OneLoginMfaException(
    override val message: String?,
    override val cause: Throwable? = null,
    val code: Int? = null,
    val errorDescription: String? = null
) : Exception(message, cause)

Storage

All registered factors are encrypted and saved in a Room database provided by the OneLoginMfa library.

Register a Factor

There are three methods to register a Factor. Whichever method you choose will depend on the type of factor you are going to use. Upon a successful factor registration attempt, a RegisterFactorSuccess object will be returned:

data class RegisterFactorSuccess(
    val id: Long // ID generated by Room
)

You can use this id to perform operations on the factor.

QR-Scan or Manual Entry

This can be used to register a Factor using a otpauth:// link directly. You can either provide the link to the framework or use the layout resources provided to let your users enter the code manually or scan a QR-Code containing the link. To register without user interaction:

// Kotlin
mfaClient.registerFactor(
    code,
    object : MfaCallback<RegisterFactorSuccess, RegisterFactorError> {
        override fun onSuccess(success: RegisterFactorSuccess) {
            Timber.d("Successfully added factor")
        }

        override fun onError(error: RegisterFactorError) {
            Timber.d("Error adding factor")
        }
    })

// Java
mfaClient.registerFactor(code, new MfaCallback<RegisterFactorSuccess, RegisterFactorError>() {
    @Override
    public void onSuccess(@NonNull RegisterFactorSuccess success) {
        Timber.d("Successfully added factor")
    }

    @Override
    public void onError(@NonNull RegisterFactorError error) {
        Timber.d("Error adding factor");
    }
});

Web-Login

This method can only be used for OneLogin factors. The account must be configured by the administrator to require OTP authentication and users without a MFA device must register before being able to log in. This will only allow the user to register if they do not have any current OneLogin Protect factors already existing, otherwise an exception will be thrown.

The variables subdomain, username, and password are required. To register:

// Kotlin
mfaClient.registerFactorByWebLogin(subdomain, username, password,
    object : MfaCallback<RegisterFactorSuccess, RegisterFactorError> {
        override fun onSuccess(success: RegisterFactorSuccess) {
            Timber.d("Successfully added OneLogin factor via web login")
        }

        override fun onError(error: RegisterFactorError) {
            Timber.d("Error adding OneLogin factor via web login")
        }
    })

// Java
mfaClient.registerFactorByWebLogin(subdomain, username, password, 
    new MfaCallback<RegisterFactorSuccess, RegisterFactorError>() {
        @Override
        public void onSuccess(@NonNull RegisterFactorSuccess success) {
            Timber.d("Successfully added OneLogin factor via web login");
        }

        @Override
        public void onError(@NonNull RegisterFactorError error) {
            Timber.d("Error adding OneLogin factor via web login");
        }
});

OIDC

Registering a factor via OIDC is not yet supported.

Retrieving and Deleting Factors

Retrieving Factors

There are three methods to retrieve registered factors.

Get factor(s):

// Kotlin
mfaClient.getFactors(object : MfaCallback<List<Factor>, Exception> {
    override fun onSuccess(success: List<Factor>) {
        for (factor in success) {
            Timber.d(factor)
        }
    }

    override fun onError(error: Exception) {
        Timber.d("Failed to retrieve factors")
    }
})

// Java
mfaClient.getFactors(new MfaCallback<List<Factor>, Exception>() {
    @Override
    public void onSuccess(@NonNull List<Factor> success) {
        for (int i = 0; i < success.size(); i++) {
            Timber.d(factor);
        }
    }

    @Override
    public void onError(@NonNull Exception error) {
        Timber.d("Failed to retrieve factors");
    }
});

Get factor by ID (this ID is generated by Room):

// Kotlin
mfaClient.getFactorById(id: Long, object : MfaCallback<Factor, Exception> {
    override fun onSuccess(success: Factor) {
        Timber.d("Retrieved factor: $success")
    }

    override fun onError(error: Exception) {
        Timber.d("Failed to retrieve factor")
    }
})

// Java
mfaClient.getFactorById(Long id, new MfaCallback<Factor, Exception>() {
    @Override
    public void onSuccess(@NonNull Factor success) {
        Timber.d("Retrieved factor: $success");
    }

    @Override
    public void onError(@NonNull Exception error) {
        Timber.d("Failed to retrieve factor");
    }
});

Get factor by credential ID (can only be used for OneLogin factors):

// Kotlin
mfaClient.getFactorByCredentialId(credentialId: String, object : MfaCallback<Factor, Exception> {
    override fun onSuccess(success: Factor) {
        Timber.d("Retrieved factor: $success")
    }

    override fun onError(error: Exception) {
        Timber.d("Failed to retrieve factor")
    }
})

// Java
mfaClient.getFactorByCredentialId(String credentialId, new MfaCallback<Factor, Exception>() {
    @Override
    public void onSuccess(@NonNull Factor success) {
        Timber.d("Retrieved factor: $success");
    }

    @Override
    public void onError(@NonNull Exception error) {
        Timber.d("Failed to retrieve factor");
    }
});

Deleting Factors

There are three methods to delete registered factors. Note that calling these methods will not remove your factor within your OneLogin account.

Delete factor(s):

// Kotlin
mfaClient.removeAllFactors(object : MfaCallback<List<Factor>, Exception> {
    override fun onSuccess(success: Int) {
        Timber.d("Removed all factors")
    }

    override fun onError(error: Exception) {
        Timber.d("Failed to remove all factors")
    }
})

// Java
mfaClient.removeAllFactors(new MfaCallback<List<Factor>, Exception>() {
    @Override
    public void onSuccess(@NonNull List<Factor> success) {
        Timber.d("Removed all factors");
    }

    @Override void onError(@NonNull Exception error)
        Timber.d("Failed to remove all factors");
    }
});

Delete factor by passing Factor:

// Kotlin
mfaClient.removeFactor(factor: Factor, object : MfaCallback<Int, Exception> {
    override fun onSuccess(success: Factor) {
        Timber.d("Removed factor")
    }

    override fun onError(error: Exception) {
        Timber.d("Failed to remove factor")
    }
})

// Java
mfaClient.removeFactor(factor, new MfaCallback<Integer, Exception>() {
    @Override
    public void onSuccess(Integer integer) {
        Timber.d("Removed factor");
    }

    @Override
    public void onError(Exception e) {
        Timber.d("Failed to remove factor");
    }
});

Delete factor by credential ID (can only be used for OneLogin factors):

// Kotlin
mfaClient.removeFactorByCredentialId(credentialId: String, object : MfaCallback<Int, Exception> {
    override fun onSuccess(success: Factor) {
        Timber.d("Removed factor")
    }

    override fun onError(error: Exception) {
        Timber.d("Failed to remove factor")
    }
})

// Java
mfaClient.removeFactor(String credentialId, new MfaCallback<Integer, Exception>() {
    @Override
    public void onSuccess(Integer integer) {
        Timber.d("Removed factor");
    }

    @Override
    public void onError(Exception e) {
        Timber.d("Failed to remove factor");
    }
});

Refresh Factors

This method should be called if you have any OneLogin factors registered in your app. This is necessary to retrieve any updated device settings or notification of a removed factor from your OneLogin account. If any settings need to be updated, the method will handle updating it within the local database. If a factor has been removed from your OneLogin account, this method will delete the factor from the local database. Ideally, this should be called before retrieving your factors for display.

To refresh factor(s):

// Kotlin
mfaClient.refreshFactors(object : MfaCallback<RefreshFactorsSuccess, Exception> {
    override fun onSuccess(success: RefreshFactorsSuccess) {
        Timber.d("Refreshed factors")
    }

    override fun onError(error: Exception) {
        Timber.d("Failed to refresh factors")
    }
})

// Java
mfaClient.refreshFactors(new MfaCallback<RefreshFactorsSuccess, Exception>() {
    @Override
    public void onSuccess(@NonNull RefreshFactorsSuccess success) {
        Timber.d("Refreshed factors");
    }

    @Override
    public void onError(@NonNull Exception error) {
        Timber.d("Failed to refresh factors");
    }
});

The RefreshFactorsSuccess object that gets returned consists of:

data class RefreshFactorsSuccess(
    var unpairedCount: Int = 0, // number of factors removed from OneLogin account
    var unpairedFactors: ArrayList<Factor> = arrayListOf(), // list of unpaired Factors
    var updatedCount: Int = 0, // number of factors that had updated device settings
    var updatedFactors: ArrayList<Factor> = arrayListOf(), // list of affected factors with updated device settings
)

MFA Usage

To retrieve a OTP from a registered factor, you can display it to the user with the provided OTP View or retrieve it yourself. Retrieve a factor's OTP by:

// Kotlin
val otpCode = myFactor.getOtp()

// Java
String otpCode = myFactor.getOtp();

Customizable Layout Resources

We've added customizable layout resources that are currently existing in our Protect application to allow for easier development. Using these layouts is not required to call the provided methods within the MfaClient.

OTP View

This is a custom view that handles displaying the OTP code for a factor. It also will update based on the device settings that are retrieved from your OneLogin account. A user can also tap on the code to copy it directly to their clip board.

To add the Otp View to your layout:

<com.onelogin.mfa.view.Otp
    android:id="@+id/my_otp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:code_color="@color/primary_blue"
    app:code_size="50sp"
    app:enable_tap_to_copy="true"
    app:enable_show_toast_on_copy="true" />

Then in your activity/fragment:

// Kotlin
val otpCodeView: Otp by lazy { findViewById(R.id.my_otp) }

otpCodeView.setFactor(myFactor)

// Java
Otp otpCodeView = findViewById(R.id.my_otp);

otpCodeView.setFactor(myFactor);

These are the custom attributes available for the Otp View:

Attribute Format Default Value Purpose
code string "" Set code without using setFactor(factor: Factor)
code_color color Default text color Set color for OTP text
code_size dimension Default text size Set size for OTP text
enable_tap_to_copy boolean true Allow user to tap on the OTP text & copy to clipboard
enable_show_toast_on_copy boolean true Show toast message when user taps on OTP to copy code
enable_warning_icon boolean true Show warning icon when OTP can't be displayed because of OneLogin device settings
warning_icon_size dimension 18dp Set size for warning icon
message_warning_size dimension Default text size Set size warning message
message_warning_keyguard_required string "Screen lock required" Set text for keyguard warning message
message_warning_unrooted_device_required string "Un-rooted device required" Set text for un-rooted device message

QR Scanner

This is a custom view that allows you to add a QR code scanner to scan OneLogin and third party QR codes to register a factor. It is required that you override onRequestPermissionsResult for the camera to be able to open and setScanListener to retrieve the code.

To add the QRScan View to your layout:

<com.onelogin.mfa.view.QrScan
    android:id="@+id/my_qr_scanner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:enable_border="true"
    app:enable_accept_symbol="true"/>

Then in your activity/fragment:

// Kotlin
val qrScannerView: QrScan by lazy { requireView().findViewById(R.id.my_qr_scanner) }

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    qrScannerView.onRequestPermissionsResult(requestCode, permissions, grantResults)
}

qrScannerView.setScanListener(object : OnCodeEntryListener {
    override fun onOneLoginCode(code: String) {
        Timber.d("Found OneLogin code. Ready to register")
    }

    override fun onThirdPartyCode(code: String) {
        Timber.d("Found 3rd party code. Ready to register")
    }
})

// Java
QrScan qrScanner = findViewById(R.id.my_qr_scanner);

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    qrScanner.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

qrScanner.setScanListener(new OnCodeEntryListener() {
    @Override
    public void onOneLoginCode(@NonNull String code) {
        Timber.d("Found OneLogin code. Ready to register");
    }

    @Override
    public void onThirdPartyCode(@NonNull String code) {
        Timber.d("Found 3rd party code. Ready to register");
    }
});

These are the custom attributes available for the QrScan View:

Attribute Format Default Value Purpose
enable_border boolean true Set to true to show a green square border on the QR scanner
enable_accept_symbol boolean true Set to true to show a green check mark when QR code is found

Manual Entry

This is a custom view that allows you to manually enter the code provided by your OneLogin account. The code is currently in the format of XX-XXXXXXX.

To add the ManualEntry View to your layout:

<com.onelogin.mfa.view.ManualEntry
    android:id="@+id/my_manual_entry_input"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:factor_code_size="30sp"
    app:factor_code_color="@color/primary_blue"
    app:factor_code_hint="Enter Activation Code"
    app:enable_nine_digit_format="true" />

Then in your activity/fragment:

// Kotlin
val manualEntryView: ManualEntry by lazy { requireView().findViewById(R.id.my_manual_entry_input) }

manualEntryView.setEntryCompleteListener(object: OnCodeEntryListener {
    override fun onOneLoginCode(code: String) {
        Timber.d("Valid OneLogin code. Ready to register.")
    }

    override fun onThirdPartyCode(code: String) {
        Timber.d("Valid 3rd party code. Ready to register.")
    }
})

// Java
ManualEntry manualEntry = findViewById(R.id.my_manual_entry_input);

manualEntry.setEntryCompleteListener(new OnCodeEntryListener() {
    @Override
    public void onOneLoginCode(@NonNull String code) {
        Timber.d("Valid OneLogin code. Ready to register.");
    }

    @Override
    public void onThirdPartyCode(@NonNull String code) {
        Timber.d("Valid 3rd party code. Ready to register.");
    }
});

These are the custom attributes available for the ManualEntry View:

Attribute Format Default Value Purpose
factor_code_color color Default text color Set color of code text input
factor_code_size dimension Default text size Set size of code text
factor_code_hint string "" Set text for code entry hint
factor_code_hint_color color Default text color Set color for code entry hint

Countdown Dial

This is a custom view that allows you to add a countdown dial that coincides with the expiration of your OTP code.

To add CountdownDial to your layout:

<com.onelogin.mfa.view.CountdownDial
    android:id="@+id/my_countdown"
    android:layout_width="25dp"
    android:layout_height="25dp"
    app:countdown_paint_color="@color/primary_orange_variant"
    app:countdown_background_color="@color/primary_grey"
    app:countdown_center_color="?android:colorBackground"/>

Then in your activity/fragment:

// Kotlin
val countdownDialView: CountdownDial = findViewById(R.id.my_countdown)

countdownDialView.setProgress(myFactor)

// Java
CountdownDial countdownDial = findViewById(R.id.my_countdown);

setProgress(myFactor, 100);

These are the custom attributes available for the CountdownDial View:

Attribute Format Default Value Purpose
factor_code_color color Default text color Set color of code text input
factor_code_size dimension Default text size Set size of code text
factor_code_hint string "" Set text for code entry hint
factor_code_hint_color color Default text color Set color for code entry hint

Running Demo App

Currently, a Kotlin demo application is available to test. Clone the project and run appkotlin in Android Studio.