Skip to main content

Android

Initial Setup

The LoginID Android SDK enables you to add passkey authentication in your native Android application without having to redirect the user to any pages outside your application.

The SDK leverages the Credential Manager API for creating and syncing passkeys with Google Password Manager.

Required settings:

  • Host Digital Asset Links JSON file
  • Base URL
info

The LoginID Android Mobile SDK requires Android 9+ (API 28+) for compatibility.

Configure assetlinks.json File

To make your app work with the SDK, you need to link your app to your website. This is done by using a file called Digital Asset Links. You must create this file and then place it on your website.

Find package name

If you need to find the package name of your Android app you can find it in your app's build.gradle file under the defaultConfig section.

Generate SHA-256 Fingerprint of Your App's Signing Key

The SHA256 fingerprint is a unique hash value generated from the app's certificate, which is used to identify the app's authenticity and integrity.

You can find your app's SHA256 fingerprint by using the keytool utility that comes with the Java Development Kit (JDK). And run the following command while replacing KEYSTORE_PATH with the path to your keystore file, KEYSTORE_ALIAS with your key alias, KEYSTORE_PASSWORD and KEY_PASSWORD are replaced with the actual passwords for your keystore and key.

keytool -list -v -keystore KEYSTORE_PATH \
-alias KEYSTORE_ALIAS \
-storepass KEYSTORE_PASSWORD \
-keypass KEY_PASSWORD

This command will output various certificate information, including the SHA256 fingerprint.

tip

The keytool command can be found at $JAVA_HOME/bin if it isn't globally available.

tip

When you're developing an app locally, it's simpler to use your default debug keystore for testing purposes. Find where your debug keystore is located and run the command on that.

Replace the arguments with the default values for the debug keystore.

  • KEYSTORE_ALIAS: androiddebugkey
  • KEYSTORE_PASSWORD: android
  • KEY_PASSWORD: android

In a production environment, you should opt for a dedicated keystore. If you need to find your SHA256 fingerprint, especially for Google Play Console related tasks, you can do so directly within the Google Play Console.

Host assetlinks.json file on Your Website Directory

Host the file under the correct path <WEBSITE_DOMAIN>/.well-known/assetlinks.json in the root directory of your website.

Here is an example of the minimum required fields in the file:

[
{
"relation": [
"delegate_permission/common.handle_all_urls",
"delegate_permission/common.get_login_creds"
],
"target": {
"namespace": "android_app",
"package_name": "<PACKAGE_NAME>",
"sha256_cert_fingerprints": [
"<SHA256_FINGERPRINT>"
]
}
}
]

Use the following example as a template and replace PACKAGE_NAME and SHA256_FINGERPRINT with your values.

Declare Association in the Android App

To associate your Android app, update your strings.xml file with the website's URL.

<resources>
<string name="asset_statements" translatable="false">
[{
\"include\": \"<WEBSITE_DOMAIN>/.well-known/assetlinks.json\"
}]
</string>
</resources>

Then modify the AndroidManifest.xml to include a meta-data element referencing this URL.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
...
</application>
</manifest>

Create Application

Create a new application to obtain your base URL.

Add Android Fingerprint to Application

Enter the SHA256 created previously found under the Fido2 section of your application.

Add SDK to Existing Application

Adding the SDK to your application currently requires a download and manual import via an .aar (Android ARchive).

Download the SDK

After creating an application, visit the Android section in the Get Started guide to download the latest SDK. The SDK is provided as an .aar file, which will be imported into your application.

Import the Android ARchive

To incorporate the SDK into your project, add the .aar file and its dependencies to your app's build.gradle file.

dependencies {
// Path to the SDK's Android Archive
implementation files('./libs/api-release.aar')

// External dependencies required by the SDK
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'androidx.credentials:credentials:1.2.0'
implementation 'androidx.credentials:credentials-play-services-auth:1.2.0'
}

Create an SDK Instance

class MyApplication : Application() {
private val config = LoginIDServiceConfig("<BASE_URL>")
LoginID.configure(config)

override fun onCreate() {
super.onCreate()
// any other configurations
// ...
// ...
}
}

API Reference

note

Java does not natively support Kotlin's coroutine features like suspending functions, requiring explicit callback mechanisms for interoperability. The Bidirectional API facilitates this by serving as a callback system, allowing Java to handle continuations and manage asynchronous operations initiated by Kotlin's suspending functions. This enables seamless integration of asynchronous behaviors between Java and Kotlin in a mixed-codebase environment.

authenticateWithPasskey

This method authenticates a user with a passkey and will trigger additional dialogs to guide the user through the process.

A short-lived authorization token is returned, allowing access to protected resources for the given user such as listing, renaming or deleting passkeys.

suspend fun authenticateWithPasskey(
activity: Activity,
username: String,
options: LoginIDOptions?
): AuthResult
ParameterTypeRequiredDetails
activityActivitytrueAn Activity reference providing the UI context for the authentication process. Required to display dialogs and to ensure the flow integrates with the Android task stack.
usernameStringtrueThe username for the account attempting to authenticate. Passing an empty string will trigger usernameless authentication, where the user can select their passkey profile to sign-in with.
optionsLoginIDOptions?falseAdditional options for the sign-in process.

data class AuthResult(
val isAuthenticated: Boolean = false,
val isFallback: Boolean = false,
val fallbackOptions: FallbackMethodsResult? = null,
val token: String? = null,
val userId: String? = null,
val passkeyId: String? = null,
val deviceId: String? = null,
)

For more details about the LoginID token, click here.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

val username = "billy@loginid.io"
val options: LoginIDOptions? = null

try {
lid.authenticateWithPasskey(this@MainActivity, username, options)
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

authenticateWithPasskeyAutofill

Authenticates a user by utilizing Google's Credential Manager passkey autofill capabilities.

A short-lived authorization token is returned, allowing access to protected resources for the given user such as listing, renaming or deleting passkeys.

suspend fun authenticateWithPasskeyAutofill(
activity: Activity,
usernameAnchorView: View,
options: LoginIDOptions?
): AuthResult
ParameterTypeRequiredDetails
activityActivitytrueAn Activity reference providing the UI context for the authentication flow. Required for displaying dialogs and integrating with the Android task stack.
usernameAnchorViewViewtrueThe view used as the anchor for passkey autofill suggestions. When focused, Credential Manager displays relevant passkey profiles. Typically this will be a EditText element.
optionsLoginIDOptions?falseAdditional options for the sign-in process.

data class AuthResult(
val isAuthenticated: Boolean = false,
val isFallback: Boolean = false,
val fallbackOptions: FallbackMethodsResult? = null,
val token: String? = null,
val userId: String? = null,
val passkeyId: String? = null,
val deviceId: String? = null,
)

For more details about the LoginID token, click here.

@Composable
fun PasskeyAutofillSample(activity: Activity) {
var token by remember { mutableStateOf("") }
var errorMessage by remember { mutableStateOf<String?>(null) }

var usernameField: EditText? by remember { mutableStateOf(null) }

// ... AndroidView that sets usernameField ...

// Automatically run when appears
LaunchedEffect(usernameField) {
val anchorView = usernameField ?: return@LaunchedEffect

val username = anchorView.text.toString()
val options = LoginIDOptions()

try {
val result = LoginIDSDK.authenticateWithPasskeyAutofill(
activity = activity,
usernameAnchorView = anchorView,
options = options
)
token = result.token
} catch (e: LoginIDError) {
errorMessage = e.message
}
}
}

confirmTransaction

This method initiates a non-repudiation signature process by generating a transaction-specific challenge and then expects the client to provide an assertion response using a passkey.

This method is useful for confirming actions such as payments or changes to sensitive account information, ensuring that the transaction is being authorized by the rightful owner of the passkey.

For a more detailed guide click here.

suspend fun confirmTransaction(
activity: Activity,
username: String,
payload: String,
options: LoginIDOptions?
): TxConfirmResult
ParameterTypeRequiredDetails
activityActivitytrueAn Activity reference providing the UI context for the authentication process. Required to display dialogs and to ensure the flow integrates with the Android task stack.
usernameStringtrueThe username for the account attempting to perform transaction confirmation.
payloadStringtrueThe transaction-specific payload, containing details like transaction amount, recipient, and other necessary metadata.
optionsLIDOptions?falseAdditional options for transaction confirmation, including transaction type and other relevant information.
options.nonceString?falseA unique nonce to ensure the transaction's integrity and prevent replay attacks
options.txTypeString?falseSpecify the type of transaction being confirmed for additional validation.

data class TxConfirmResult(
val token: String? = null,
val credentialId: String? = null,
val authCred: LoginIDPasskey? = null,
)

For more details on validating the transaction token, see the transaction confirmation guide.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

val username = "billy@loginid.io"
val txPayload = "999999"
val nonce = "f846bb01-492e-422b-944a-44b04adc441e"
val options: LoginIDOptions? = LoginIDOptions(nonce = nonce)

try {
val result = lid.confirmTransaction(this@MainActivity, username, txPayload, options)
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

createPasskey

This method helps to create a passkey. The only required parameter is the username, but additional attributes can be provided in the options parameter. Note: While the authorization token is optional, it must always be used in a production environment. You can skip it during development by adjusting the app configuration in the LoginID dashboard.

A short-lived authorization token is returned, allowing access to protected resources for the given user such as listing, renaming or deleting passkeys.

Requires one of the following to add a new passkey to an existing user:

suspend fun createPasskey(
activity: Activity,
username: String,
options: LoginIDOptions?
): AuthResult
ParameterTypeRequiredDetails
activityActivitytrueAn Activity reference providing the UI context for the authentication process. Required to display dialogs and to ensure the flow integrates with the Android task stack.
usernameStringtrueThe username for the account being registered.
optionsLoginIDOptions?falseAdditional options for the passkey creation process.
options.displayNameString?falseA human-palatable name for the user account, intended only for display on your passkeys and modals.
options.usernameTypeString?falseSpecify username type validation. Types include email, phone, or other.
options.tokenString?falseAuthorization token for passkey creation with the scope reg:write.

data class AuthResult(
val isAuthenticated: Boolean = false,
val isFallback: Boolean = false,
val fallbackOptions: FallbackMethodsResult? = null,
val token: String? = null,
val userId: String? = null,
val passkeyId: String? = null,
val deviceId: String? = null,
)

For more details about the LoginID token, click here.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

val username = "billy@loginid.io"
val options: LoginIDOptions? = null

try {
lid.createPasskey(this@MainActivity, username, options)
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

deletePasskey

Delete a specified passkey by ID from LoginID. The user must be fully authorized for this call to succeed.

Requires one of the following:

suspend fun deletePasskey(
id: String
options: LoginIDOptions?
)
ParameterTypeRequiredDetails
idStringtrueThe UUID of the passkey attempting to delete.
optionsLoginIDOptions?falseAdditional options for the delete passkey process.
options.tokenString?falseThe user's authorization token with the scope passkey:write.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

val passkeyId = "4df206a7-fbe2-4521-93a1-3db51ed0ee27"
val options: LoginIDOptions? = null

try {
lid.deletePasskey(passkeyId, options)
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

getSessionInfo

Check whether the user of the current session is authenticated and returns user info. This info is retrieved locally and no requests to backend are made.

fun getSessionInfo(): SessionInfo?

Retrieves the session data SessionInfo or nil if not set.

data class SessionInfo(
val username: String,
val id: String,
)

val user = lid.getSessionInfo();

listPasskeys

This method returns list of passkeys associated with the current user. The user must be fully authorized for this call to succeed.

Requires one of the following:

suspend fun listPasskeys(
options: LoginIDOptions?
): List<LoginIDPasskey>
ParameterTypeRequiredDetails
optionsLoginIDOptions?falseAdditional options for the list passkeys process.
options.tokenString?falseThe user's authorization token with the scope passkey:read.

data class LoginIDPasskey(
val aaguid: String,
val createdAt: String,
val id: String,
val name: String,
val credentialSynced: Boolean? = false,
val lastUsedAt: String?,
val providerName: String?,
val lastUsedFromDevice: DeviceInfo?,
)

An array of the user's passkeys.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

val options: LoginIDOptions? = null

try {
val passkeys = lid.listPasskeys(options)
for (passkey in passkeys) {

}
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

logout

Clears current user session. This method is executed locally and it just deletes authorization token from local persistant storage.

fun logout()

lid.logout()

renamePasskey

Renames a specified passkey by ID. The user must be fully authorized for this call to succeed.

Requires one of the following:

suspend fun renamePasskey(
id: String,
name: String,
options: LoginIDOptions?
)
ParameterTypeRequiredDetails
idStringtrueThe UUID of the passkey attempting to rename.
nameStringtrueThe new name for the passkey.
optionsLoginIDOptions?falseAdditional options for the renaming passkey process.
options.tokenString?falseThe user's authorization token with the scope passkey:write.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

val passkeyId = "4df206a7-fbe2-4521-93a1-3db51ed0ee27"
val newName = "My Favourite Passkey"
val options: LoginIDOptions? = null

try {
lid.renamePasskey(passkeyId, newName, options)
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

requestAndSendOtp

This method requests an OTP from the backend to be sent via the selected method. The method of delivery should be based on the user's choice from the list of available options. This can be found in the result of authenticateWithPasskey method as fallbackOptions.

The username must have either a valid email or phone profile for the method to work.

Use the validateOtp method to verify the code sent to the user.

For further implementation details have a look at the cross device authentication guide.

suspend fun requestAndSendOtp(
username: String,
method: MessageType = MessageType.EMAIL,
options: LoginIDOptions? = null
)
ParameterTypeRequiredDetails
usernameStringtrueThe username for the account to which the code will be sent.
methodMessageTypefalseThe method to use for sending the code. Valid values are email (default) and sms.
optionsLoginIDOptions?falseAdditional options for sending the code.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

val username = "billy@loginid.io"
val options: LoginIDOptions? = null

try {
lid.requestAndSendOtp(username, MessageType.EMAIL, options)
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

requestOtp

This method returns a one-time OTP to be displayed on the current device. The user must be authenticated on this device. The OTP is meant for cross-authentication, where the user reads the OTP from the screen and enters it on the target device.

Use the validateOtp method to verify the code sent to the user.

For further implementation details have a look at the cross device authentication guide.

suspend fun requestOtp(
activity: Activity,
username: String,
options: LoginIDOptions? = null
): OTPResult
ParameterTypeRequiredDetails
activityActivitytrueAn Activity reference providing the UI context for the authentication process. Required to display dialogs and to ensure the flow integrates with the Android task stack.
usernameStringtrueThe username used for passkey authentication and OTP request.
optionsLoginIDOptions?falseAdditional options for request.
options.tokenString?falseThe user's authorization token with the scope reg:write.

data class OTPResult(
val code: String,
val expiresAt: String
)

Where code is the OTP used for other forms of authentication and recovery flows.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

val username = "billy@loginid.io"
val options: LoginIDOptions? = null

try {
val response = lid.requestOtp(this@MainActivity, username, options)

print("Result with authorized OTP: " + result?.code ?: "")
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

validateOtp

This method verifies the OTP and returns an authorization token, which can be used with the passkeyCreate() method to create a new passkey. The authorization token has a short validity period and should be used immediately.

Have a look at the recovery flow guide on how to implement this feature.

suspend fun validateOtp(
username: String,
code: String,
options: LoginIDOptions? = null
): AuthResult
ParameterTypeRequiredDetails
usernameStringtrueThe username for the account attempting to authenticate.
codeStringtrueThe OTP code to authenticate.
optionsLoginIDOptions?falseAdditional options for the authentication process.

data class AuthResult(
val isAuthenticated: Boolean = false,
val isFallback: Boolean = false,
val fallbackOptions: FallbackMethodsResult? = null,
val token: String? = null,
val userId: String? = null,
val passkeyId: String? = null,
val deviceId: String? = null,
)

For more details about the LoginID token, click here.

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

// Can be obtained from `requestOtp` or backend API
val otp = "123456"
val username = "billy@loginid.io"
val options: LoginIDOptions? = null

try {
val response = lid.validateOtp(username, otp, options)
// Handle success
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

verifyConfigSettings

Validates the application's configuration settings and provides a suggested correction if any issues are detected.

suspend fun verifyConfigSettings(): LoginIDConfigResult?

data class LoginIDConfigResult(
val code: String,
val message: String,
val solution: String,
)

val config = LoginIDServiceConfig(this@MainActivity, "<BASE_URL>")
val lid = LoginID.configure(config).client()

try {
val response = lid.verifyConfigSettings()
// Handle success
if (result != null) {
println("Configuration verification returned an error:")
println("Message: ${result.message}")
println("Solution: ${result.solution}")
} else {
println("Configuration verified successfully.")
}
} catch (e: Exception) {
if (e is LoginIDError) {
// handle LoginIDError
}
}

Errors

LoginIDError

Can occur during the authentication process. It is designed to encapsulate detailed information about login-related errors, making it easier to handle and debug issues related to user authentication.

FieldTypeDetails
msgCodestringThe error code associated with the login error. Defaults to unknown_error.
msgstringThe detailed message or description of the error. Defaults to unknown error.
messgagestringThe detailed message or description of the error. Defaults to unknown error.

...
...

} catch (error: Exception) {
if (error is LoginIDError) {
print("Failed with error: ${error.msg}")
print("Failed with error code: ${error.msgCode}")
}
}