OATH Module

May 5, 2026 · View on GitHub

Ping Identity

OATH Module

The OATH MFA module provides functionality for implementing Time-based One-Time Password (TOTP) and HMAC-based One-Time Password (HOTP) multi-factor authentication in Android applications. This module enables applications to manage OATH credentials and generate one-time passwords for authentication.

Getting Started

Prerequisites

Installation

To integrate this module into your Android project, include the following dependency in your build.gradle.kts (or build.gradle) file:

dependencies {
    implementation("com.pingidentity.sdks:oath:<version>")
}

Replace <version> with the latest available version of the SDK from the Maven repository. Ensure your project's repositories block includes Maven Central or the Ping Identity Maven repository.

Usage

Initialize the OATH Client

Before using the OATH MFA functionality, you need to initialize the OathClient. There are several ways to create and initialize an OathClient:

Basic Initialization

// Create a client with default configuration (not initialized)
val oathClient = OathClient()

// Initialize the client
oathClient.initialize()

Initialize with Custom Configuration

// Create with custom configuration using DSL-style builder
val oathClient = OathClient {
    enableCredentialCache = true
    // Any other configuration options
}

The DSL-style initialization automatically calls initialize() for you.

Add a Credential from URI

Add a new OATH credential from a URI:

val uri = "otpauth://totp/Example:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30"

// Using onSuccess/onFailure callbacks
oathClient.addCredentialFromUri(uri).onSuccess { credential ->
    // Handle the successfully created credential
    println("Created credential: ${credential.issuer}")
}.onFailure { exception ->
    // Handle error
    println("Failed to add credential: ${exception.message}")
}

// Alternative: Using getOrThrow()
try {
    val credential = oathClient.addCredentialFromUri(uri).getOrThrow()
    // Use credential
} catch (e: Exception) {
    // Handle exception
}

// Alternative: Using getOrNull() for a nullable result
val credential = oathClient.addCredentialFromUri(uri).getOrNull()
if (credential != null) {
    // Use credential
} else {
    // Handle null case
}

Generate OTP Code

Generate a one-time password for an OATH credential:

// Generate code for a credential by its ID
oathClient.generateCode(credentialId).onSuccess { code ->
    // Use the generated code
    displayCode(code)
}.onFailure { exception ->
    // Handle error
    showError("Failed to generate code: ${exception.message}")
}

// With timing information
oathClient.generateCodeWithValidity(credentialId).onSuccess { codeInfo ->
    displayCode(codeInfo.code)
    updateProgressBar(codeInfo.progress)
    startCountdown(codeInfo.timeRemaining)
}

Retrieve Credentials

// Get a specific credential by ID
oathClient.getCredential(credentialId).onSuccess { credential ->
    if (credential != null) {
        // Credential found, use it
        displayCredential(credential)
    } else {
        // Credential not found
        showMessage("Credential not found")
    }
}

// Get all stored credentials
oathClient.getCredentials().onSuccess { credentials ->
    if (credentials.isEmpty()) {
        showMessage("No credentials found")
    } else {
        displayCredentials(credentials)
    }
}

Update a Credential

// Update a credential's properties
credential.displayAccountName = "John Doe" // Change the display account name

oathClient.saveCredential(credential).onSuccess { updatedCredential ->
    // Handle successful update
    showMessage("Credential updated")
}.onFailure { exception ->
    // Handle failure
    showError("Failed to update credential: ${exception.message}")
}

Delete a Credential

// Remove a credential by ID
oathClient.deleteCredential(credentialId).onSuccess { isDeleted ->
    if (isDeleted) {
        showMessage("Credential deleted")
    } else {
        showMessage("Credential not found")
    }
}.onFailure { exception ->
    showError("Failed to delete credential: ${exception.message}")
}

Extended Implementation Example

class OathAuthActivity : AppCompatActivity() {
    private lateinit var oathClient: OathMfaClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_oath_auth)

        // Initialize OATH client
        lifecycleScope.launch {
            try {
                oathClient = OathClient {
                    enableCredentialCache = true
                }
                
                // Load all credentials
                loadCredentials()
            } catch (e: Exception) {
                showError("Failed to initialize: ${e.message}")
            }
        }

        // Set up button to add new credential
        btnAddCredential.setOnClickListener {
            val uri = editTextUri.text.toString()
            lifecycleScope.launch {
                oathClient.addCredentialFromUri(uri).onSuccess { credential ->
                    showMessage("Credential added: ${credential.issuer}")
                    loadCredentials() // Refresh list
                }.onFailure { e ->
                    showError("Failed to add credential: ${e.message}")
                }
            }
        }
    }

    private fun loadCredentials() {
        lifecycleScope.launch {
            oathClient.getCredentials().onSuccess { credentials ->
                // Update UI with credentials list
                credentialsAdapter.submitList(credentials)
            }.onFailure { e ->
                showError("Failed to load credentials: ${e.message}")
            }
        }
    }

    private fun generateCodeForCredential(credentialId: String) {
        lifecycleScope.launch {
            oathClient.generateCode(credentialId).onSuccess { code ->
                // Display code to user
                textViewCode.text = code
            }.onFailure { e ->
                showError("Failed to generate code: ${e.message}")
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycleScope.launch {
            oathClient.close()
        }
    }
}

Internal Storage

The OATH module uses the MFA common storage infrastructure to securely store credentials. By default:

  • Credentials are encrypted using the Android KeyStore system
  • Credentials are persisted in a SQLite database
  • Sensitive data is never stored in plain text

Customizing Storage with SQLOathStorage

You can customize the storage behavior by creating a custom instance of SQLOathStorage and passing it to the OathClient:

// Create a custom storage instance with specific parameters
val customStorage = SQLOathStorage {
    context = applicationContext
    databaseName = "my_custom_oath_db.db"
    passphraseProvider = NonePassphraseProvider()
}

// Create the client with the custom storage
val oathClient = OathClient {
    storage = customStorage
    enableCredentialCache = true
}

The custom storage options include:

  • context: Android application context (required)
  • databaseName: Custom database name for the SQLite database (optional)
  • databaseVersion: Custom database version (default: 1)
  • passphraseProvider: Custom passphrase provider for database encryption (default: KeyStorePassphraseProvider for encrypted storage, use NonePassphraseProvider() for unencrypted storage)

Error Handling

The OATH module uses Kotlin's Result API for error handling, providing a more functional approach:

// Using onSuccess/onFailure
oathClient.addCredentialFromUri(uri)
    .onSuccess { credential ->
        // Success path
    }
    .onFailure { exception ->
        when (exception) {
            is IllegalArgumentException -> // Handle invalid URI format
            is MfaException -> // Handle general MFA errors
            else -> // Handle other exceptions
        }
    }

// Using fold for combined handling
oathClient.addCredentialFromUri(uri).fold(
    onSuccess = { credential -> 
        // Handle success
    },
    onFailure = { exception ->
        // Handle failure
    }
)

// Using runCatching for additional operations
runCatching { 
    oathClient.addCredentialFromUri(uri).getOrThrow()
}.onSuccess { credential ->
    // Do something with credential
}.onFailure { exception ->
    // Handle error
}

Advanced Usage

Custom Storage Implementation

You can implement a custom storage solution as alternative to the default SQLOathStorage by implementing the OathStorage interface:

class MyCustomStorage : OathStorage {
    override fun initialize() {
        // Initialize your custom storage
    }
    
    override fun close() {
        // Close storage
    }
    
    override fun clear() {
        // Remove all data
    }
    
    override fun storeOathCredential(credential: OathCredential) {
        // Store credential data
    }
    
    override fun retrieveOathCredential(credentialId: String): OathCredential? {
        // Retrieve credential data
        return null
    }
    
    override fun getAllOathCredentials(): List<OathCredential> {
        // Retrieve all credentials of a type
        return emptyList()
    }
    
    override fun removeOathCredential(credentialId: String): Boolean {
        // Delete credential data
        return true
    }
    
    override fun clearOathCredentials() {
        // Clear all credentials of a type
    }
}

The SharedPrefsOathStorage is a simple reference implementation that uses Android's SharedPreferences for storage, but it is not recommended for sensitive data like OATH credentials.

License

This software may be modified and distributed under the terms of the MIT license. See the LICENSE file for details.

© Copyright 2025-2026 Ping Identity Corporation. All rights reserved.