Biometric Authentication or Fingerprint Authentication in Android Kotlin.

Harshita Bambure
8 min readOct 19, 2021

--

Nowadays we used fingerprint sensors in our mobile so we used biometric to unlock the mobile device or unlock any application. So today we will learn how to code for biometric authentication or how to log in with biometrics in android kotlin.

First of all, we need to add dependency in the build.Gradle(:app) file.

implementation "androidx.biometric:biometric:1.1.0"

implementation "androidx.fragment:fragment-ktx:1.3.6"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "com.google.code.gson:gson:2.8.6"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31"
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'

After that, we will add a build feature for view binding in the build.Gradle(:app) file.

android {
buildFeatures {
viewBinding = true
}

I also added a plugin in the build.Gradle(:app)file.

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}

Now we will create dimens.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="standard_padding">32dp</dimen>
<dimen name="fab_margin">16dp</dimen>

</resources>

Add the string in the strings.xml file

<resources>
<string name="username_hint">phone number, email, or username</string>
<string name="invalid_username">Not a valid username</string>
<string name="invalid_password">Password must be >5 characters</string>
<string name="password">Password</string>
<string name="btn_login">Login</string>
<string name="already_signedin">You are now done with login. Move to your real MainActivity</string>
<string name="title_activity_enable_biometric_login">Enable BiometricLogin</string>
<string name="enable_biometric_login">Enable Biometric Login</string>
<string name="desc_biometrics_authorization">Enter your login ID and password to confirm activation of Biometric Login.</string>
<string name="cancel">Cancel</string>
<string name="btn_authorize">Authorize</string>
<string name="prompt_info_title">Sample App Authentication</string>
<string name="prompt_info_subtitle">Please login to get access</string>
<string name="prompt_info_description">Sample App is using Android biometric authentication</string>
<string name="prompt_info_use_app_password">Use app password</string>
<string name="secret_key_name">biometric_sample_encryption_key</string>
</resources>

Now we will create a Login Activity and let's start with the design part first.

activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">

<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textAppearance="?android:attr/textAppearanceLarge"
app:layout_constraintBottom_toBottomOf="@id/guideline"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.33" />

<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_padding"
android:hint="@string/username_hint"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/guideline" />

<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_padding"
android:hint="@string/password"
android:imeOptions="actionDone"
android:inputType="textPassword"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/username" />

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_padding"
android:text="@string/btn_login"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/password" />

<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/use_biometrics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/standard_padding"
android:layout_marginTop="16dp"
android:layout_marginRight="@dimen/standard_padding"
android:text="Use biometrics"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#0000EE"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login" />

<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/success"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_padding"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/colorPrimaryDark"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/use_biometrics"
tools:text="@string/already_signedin" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now we will create the activity EnableBiometricLoginActivity.

activity_enable_biometric_login.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".EnableBiometricLoginActivity">

<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_padding"
android:text="@string/enable_biometric_login"
android:textAppearance="?android:attr/textAppearanceLarge"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_padding"
android:text="@string/desc_biometrics_authorization"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />

<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_padding"
android:hint="@string/username_hint"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/description" />

<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_padding"
android:hint="@string/password"
android:imeOptions="actionDone"
android:inputType="textPassword"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/username" />

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_padding"
android:text="@string/cancel"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/authorize"
app:layout_constraintTop_toBottomOf="@id/password" />

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/authorize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_padding"
android:text="@string/btn_authorize"
app:layout_constraintLeft_toRightOf="@+id/cancel"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/password" />

</androidx.constraintlayout.widget.ConstraintLayout>

We need a data class so we will create a data class named as login result.

LoginResult.kt

data class LoginResult(val success: Boolean = false)

Now create LoginFormState.kt file

package com.example.biometricauthentication

sealed class LoginFormState

data class FailedLoginFormState(
val usernameError: Int? = null,
val passwordError: Int? = null
) : LoginFormState()

data class SuccessfulLoginFormState(
val isDataValid: Boolean = false
) : LoginFormState()

SampleAppUser.kt file

package com.example.biometricauthentication

object SampleAppUser {
var fakeToken: String? = null
var username: String? = null
}

Create Constants.kt file

package com.example.biometricauthentication

const val SHARED_PREFS_FILENAME = "biometric_prefs"
const val CIPHERTEXT_WRAPPER = "ciphertext_wrapper"

Now create view model class LoginViewModel.kt

package com.example.biometricauthentication

import android.util.Patterns
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class LoginViewModel : ViewModel() {

private val _loginForm = MutableLiveData<LoginFormState>()
val loginWithPasswordFormState: LiveData<LoginFormState> = _loginForm

private val _loginResult = MutableLiveData<LoginResult>()
val loginResult: LiveData<LoginResult> = _loginResult

fun onLoginDataChanged(username: String, password: String) {
if (!isUserNameValid(username)) {
_loginForm.value = FailedLoginFormState(usernameError = R.string.invalid_username)
} else if (!isPasswordValid(password)) {
_loginForm.value = FailedLoginFormState(passwordError = R.string.invalid_password)
} else {
_loginForm.value = SuccessfulLoginFormState(isDataValid = true)
}
}

// A placeholder username validation check
private fun isUserNameValid(username: String): Boolean {
return if (username.contains('@')) {
Patterns.EMAIL_ADDRESS.matcher(username).matches()
} else {
username.isNotBlank()
}
}

// A placeholder password validation check
private fun isPasswordValid(password: String): Boolean {
return password.length > 5
}

fun login(username: String, password: String) {
if (isUserNameValid(username) && isPasswordValid(password)) {
SampleAppUser.username = username
SampleAppUser.fakeToken = java.util.UUID.randomUUID().toString()
_loginResult.value = LoginResult(true)
} else {
_loginResult.value = LoginResult(false)
}
}
}

Now create class CryptographyManager.kt file

package com.example.biometricauthentication

import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import com.google.gson.Gson
import java.nio.charset.Charset
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

interface CryptographyManager {

fun getInitializedCipherForEncryption(keyName: String): Cipher

fun getInitializedCipherForDecryption(keyName: String, initializationVector: ByteArray): Cipher

/**
* The Cipher created with [getInitializedCipherForEncryption] is used here
*/
fun encryptData(plaintext: String, cipher: Cipher): CiphertextWrapper

/**
* The Cipher created with [getInitializedCipherForDecryption] is used here
*/
fun decryptData(ciphertext: ByteArray, cipher: Cipher): String

fun persistCiphertextWrapperToSharedPrefs(
ciphertextWrapper: CiphertextWrapper,
context: Context,
filename: String,
mode: Int,
prefKey: String
)

fun getCiphertextWrapperFromSharedPrefs(
context: Context,
filename: String,
mode: Int,
prefKey: String
): CiphertextWrapper?

}

fun CryptographyManager(): CryptographyManager = CryptographyManagerImpl()


private class CryptographyManagerImpl : CryptographyManager {

private val KEY_SIZE = 256
private val ANDROID_KEYSTORE = "AndroidKeyStore"
private val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
private val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
private val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES

override fun getInitializedCipherForEncryption(keyName: String): Cipher {
val cipher = getCipher()
val secretKey = getOrCreateSecretKey(keyName)
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
return cipher
}

override fun getInitializedCipherForDecryption(
keyName: String,
initializationVector: ByteArray
): Cipher {
val cipher = getCipher()
val secretKey = getOrCreateSecretKey(keyName)
cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, initializationVector))
return cipher
}

override fun encryptData(plaintext: String, cipher: Cipher): CiphertextWrapper {
val ciphertext = cipher.doFinal(plaintext.toByteArray(Charset.forName("UTF-8")))
return CiphertextWrapper(ciphertext, cipher.iv)
}

override fun decryptData(ciphertext: ByteArray, cipher: Cipher): String {
val plaintext = cipher.doFinal(ciphertext)
return String(plaintext, Charset.forName("UTF-8"))
}

private fun getCipher(): Cipher {
val transformation = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING"
return Cipher.getInstance(transformation)
}

private fun getOrCreateSecretKey(keyName: String): SecretKey {
// If Secretkey was previously created for that keyName, then grab and return it.
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null) // Keystore must be loaded before it can be accessed
keyStore.getKey(keyName, null)?.let { return it as SecretKey }

// if you reach here, then a new SecretKey must be generated for that keyName
val paramsBuilder = KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
paramsBuilder.apply {
setBlockModes(ENCRYPTION_BLOCK_MODE)
setEncryptionPaddings(ENCRYPTION_PADDING)
setKeySize(KEY_SIZE)
setUserAuthenticationRequired(true)
}

val keyGenParams = paramsBuilder.build()
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
ANDROID_KEYSTORE
)
keyGenerator.init(keyGenParams)
return keyGenerator.generateKey()
}

override fun persistCiphertextWrapperToSharedPrefs(
ciphertextWrapper: CiphertextWrapper,
context: Context,
filename: String,
mode: Int,
prefKey: String
) {
val json = Gson().toJson(ciphertextWrapper)
context.getSharedPreferences(filename, mode).edit().putString(prefKey, json).apply()
}

override fun getCiphertextWrapperFromSharedPrefs(
context: Context,
filename: String,
mode: Int,
prefKey: String
): CiphertextWrapper? {
val json = context.getSharedPreferences(filename, mode).getString(prefKey, null)
return Gson().fromJson(json, CiphertextWrapper::class.java)
}
}


data class CiphertextWrapper(val ciphertext: ByteArray, val initializationVector: ByteArray)

Now create an object file for BiometricPromptUtils.kt

package com.example.biometricauthentication

import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat


object BiometricPromptUtils {
private const val TAG = "BiometricPromptUtils"
fun createBiometricPrompt(
activity: AppCompatActivity,
processSuccess: (BiometricPrompt.AuthenticationResult) -> Unit
): BiometricPrompt {
val executor = ContextCompat.getMainExecutor(activity)

val callback = object : BiometricPrompt.AuthenticationCallback() {

override fun onAuthenticationError(errCode: Int, errString: CharSequence) {
super.onAuthenticationError(errCode, errString)
Log.d(TAG, "errCode is $errCode and errString is: $errString")
}

override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.d(TAG, "User biometric rejected.")
}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Log.d(TAG, "Authentication was successful")
processSuccess(result)
}
}
return BiometricPrompt(activity, executor, callback)
}

fun createPromptInfo(activity: AppCompatActivity): BiometricPrompt.PromptInfo =
BiometricPrompt.PromptInfo.Builder().apply {
setTitle(activity.getString(R.string.prompt_info_title))
setSubtitle(activity.getString(R.string.prompt_info_subtitle))
setDescription(activity.getString(R.string.prompt_info_description))
setConfirmationRequired(false)
setNegativeButtonText(activity.getString(R.string.prompt_info_use_app_password))
}.build()
}

LoginActivity.kt

package com.example.biometricauthentication

import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.activity.viewModels
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.Observer
import com.example.biometricauthentication.databinding.ActivityLoginBinding
import kotlinx.android.synthetic.main.activity_login.*

class LoginActivity : AppCompatActivity() {
private val TAG = "LoginActivity"
private lateinit var biometricPrompt: BiometricPrompt
private val cryptographyManager = CryptographyManager()
private val ciphertextWrapper
get() = cryptographyManager.getCiphertextWrapperFromSharedPrefs(
applicationContext,
SHARED_PREFS_FILENAME,
Context.MODE_PRIVATE,
CIPHERTEXT_WRAPPER
)
private lateinit var binding: ActivityLoginBinding
private val loginWithPasswordViewModel by viewModels<LoginViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)

val canAuthenticate = BiometricManager.from(applicationContext).canAuthenticate()
if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {
binding.useBiometrics.visibility = View.VISIBLE
binding.useBiometrics.setOnClickListener {
if (ciphertextWrapper != null) {
showBiometricPromptForDecryption()
} else {
startActivity(Intent(this, EnableBiometricLoginActivity::class.java))
}
}
} else {
binding.useBiometrics.visibility = View.INVISIBLE
}

if (ciphertextWrapper == null) {
setupForLoginWithPassword()
}

login.setOnClickListener {
startActivity(Intent(this, MainActivity::class.java))
}
}


override fun onResume() {
super.onResume()

if (ciphertextWrapper != null) {
if (SampleAppUser.fakeToken == null) {
showBiometricPromptForDecryption()
} else {
startActivity(Intent(this, MainActivity::class.java))
}
}
}

// BIOMETRICS SECTION

private fun showBiometricPromptForDecryption() {
ciphertextWrapper?.let { textWrapper ->
val secretKeyName = getString(R.string.secret_key_name)
val cipher = cryptographyManager.getInitializedCipherForDecryption(
secretKeyName, textWrapper.initializationVector
)
biometricPrompt =
BiometricPromptUtils.createBiometricPrompt(
this,
::decryptServerTokenFromStorage
)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
}

private fun decryptServerTokenFromStorage(authResult: BiometricPrompt.AuthenticationResult) {
ciphertextWrapper?.let { textWrapper ->
authResult.cryptoObject?.cipher?.let {
val plaintext =
cryptographyManager.decryptData(textWrapper.ciphertext, it)
SampleAppUser.fakeToken = plaintext
// Now that you have the token, you can query server for everything else
// the only reason we call this fakeToken is because we didn't really get it from
// the server. In your case, you will have gotten it from the server the first time
// and therefore, it's a real token.

startActivity(Intent(this, MainActivity::class.java))
}
}
}

// USERNAME + PASSWORD SECTION
private fun setupForLoginWithPassword() {
loginWithPasswordViewModel.loginWithPasswordFormState.observe(this, Observer { formState ->
val loginState = formState ?: return@Observer
when (loginState) {
is SuccessfulLoginFormState -> binding.login.isEnabled = loginState.isDataValid
is FailedLoginFormState -> {
loginState.usernameError?.let { binding.username.error = getString(it) }
loginState.passwordError?.let { binding.password.error = getString(it) }
}
}
})
loginWithPasswordViewModel.loginResult.observe(this, Observer {
val loginResult = it ?: return@Observer
if (loginResult.success) {
updateApp(
"You successfully signed up using password as: user " +
"${SampleAppUser.username} with fake token ${SampleAppUser.fakeToken}"
)
}
})
binding.username.doAfterTextChanged {
loginWithPasswordViewModel.onLoginDataChanged(
binding.username.text.toString(),
binding.password.text.toString()
)
}
binding.password.doAfterTextChanged {
loginWithPasswordViewModel.onLoginDataChanged(
binding.username.text.toString(),
binding.password.text.toString()
)
}
binding.password.setOnEditorActionListener { _, actionId, _ ->
when (actionId) {
EditorInfo.IME_ACTION_DONE ->
loginWithPasswordViewModel.login(
binding.username.text.toString(),
binding.password.text.toString()
)
}
false
}
binding.login.setOnClickListener {
loginWithPasswordViewModel.login(
binding.username.text.toString(),
binding.password.text.toString()
)
}
Log.d(TAG, "Username ${SampleAppUser.username}; fake token ${SampleAppUser.fakeToken}")
}

private fun updateApp(successMsg: String) {
binding.success.text = successMsg
}
}

Here I create the main activity for my home page so create one activity.

Create EnableBiometricLoginActivity.kt

package com.example.biometricauthentication

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.inputmethod.EditorInfo
import androidx.activity.viewModels
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.Observer
import com.example.biometricauthentication.databinding.ActivityEnableBiometricLoginBinding

class EnableBiometricLoginActivity : AppCompatActivity() {

private val TAG = "EnableBiometricLogin"
private lateinit var cryptographyManager: CryptographyManager
private val loginViewModel by viewModels<LoginViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityEnableBiometricLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.cancel.setOnClickListener { finish() }

loginViewModel.loginWithPasswordFormState.observe(this, Observer { formState ->
val loginState = formState ?: return@Observer
when (loginState) {
is SuccessfulLoginFormState -> binding.authorize.isEnabled = loginState.isDataValid
is FailedLoginFormState -> {
loginState.usernameError?.let { binding.username.error = getString(it) }
loginState.passwordError?.let { binding.password.error = getString(it) }
}
}
})
loginViewModel.loginResult.observe(this, Observer {
val loginResult = it ?: return@Observer
if (loginResult.success) {
showBiometricPromptForEncryption()
}
})
binding.username.doAfterTextChanged {
loginViewModel.onLoginDataChanged(
binding.username.text.toString(),
binding.password.text.toString()
)
}
binding.password.doAfterTextChanged {
loginViewModel.onLoginDataChanged(
binding.username.text.toString(),
binding.password.text.toString()
)
}
binding.password.setOnEditorActionListener { _, actionId, _ ->
when (actionId) {
EditorInfo.IME_ACTION_DONE ->
loginViewModel.login(
binding.username.text.toString(),
binding.password.text.toString()
)
}
false
}
binding.authorize.setOnClickListener {
loginViewModel.login(binding.username.text.toString(), binding.password.text.toString())
}
}

private fun showBiometricPromptForEncryption() {
val canAuthenticate = BiometricManager.from(applicationContext).canAuthenticate()
if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {
val secretKeyName = getString(R.string.secret_key_name)
cryptographyManager = CryptographyManager()
val cipher = cryptographyManager.getInitializedCipherForEncryption(secretKeyName)
val biometricPrompt =
BiometricPromptUtils.createBiometricPrompt(this, ::encryptAndStoreServerToken)
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
}

private fun encryptAndStoreServerToken(authResult: BiometricPrompt.AuthenticationResult) {
authResult.cryptoObject?.cipher?.apply {
SampleAppUser.fakeToken?.let { token ->
Log.d(TAG, "The token from server is $token")
val encryptedServerTokenWrapper = cryptographyManager.encryptData(token, this)
cryptographyManager.persistCiphertextWrapperToSharedPrefs(
encryptedServerTokenWrapper,
applicationContext,
SHARED_PREFS_FILENAME,
Context.MODE_PRIVATE,
CIPHERTEXT_WRAPPER
)
}
}
finish()
}

}

Source code.

That's it. Happy coding!!

--

--

Harshita Bambure
Harshita Bambure

Written by Harshita Bambure

Android Developer || WomenTech Global Ambassador at WomenTech Network. || Yoga Teacher || Member @WTM .

No responses yet