OATH Module
May 5, 2026 · View on GitHub
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
- Ping Advanced Identity Cloud / PingAM Supported Versions
- Android API level 29 or higher
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.