Keyri Android SDK
The latest source code of the Keyri Android SDK can be found here: https://github.com/Keyri-Co/keyri-android-whitelabel-sdk/releases (opens in a new tab)
System Requirements
-
Android API level 23 or higher
-
AndroidX compatibility
-
Kotlin coroutines compatibility
Note: Your app does not have to be written in Kotlin to integrate this SDK, but it must be able to depend on Kotlin functionality.
Integration
Add SDK dependency to your module build.gradle
file and sync project:
dependencies {
// ...
implementation("com.keyri:keyrisdk:$latestKeyriVersion")
}
Option 1 - App Links
To handle Android App Links (e.g., for QR login straight from the user's built-in camera app) you need to define the following intent-filter block in your AndroidManifest.xml
:
<application...>
<!-- ... -->
<activity...>
<!-- ... -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="${domainName}" android:scheme="https" />
</intent-filter>
</activity>
</application>
This will handle all links with the following scheme: https://{yourCompany}.onekey.to?sessionId={sessionId}
Note: Keyri will create your https://{yourCompany}.onekey.to
page automatically once you configure it in the dashboard (opens in a new tab)
In the activity where the processing of links is declared, you need to add handlers in the onNewIntent()
and onCreate()
methods, and pass sessionId
to easyKeyriAuth
method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent.data?.let(::process)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
process(intent.data)
}
private fun processLink(data: Uri?) {
data?.getQueryParameters("sessionId")?.firstOrNull()?.let { sessionId ->
lifecycleScope.launch(Dispatchers.IO) {
val result = Keyri(this@MainActivity).easyKeyriAuth(
supportFragmentManager,
sessionId,
"[Your appKey]", // Get this value from the Keyri Developer Portal
"Custom payload here",
"public-User-Id", // publicUserId is optional
)
// Process result
}
} ?: Log.e("Keyri", "Failed to process link")
}
Alternatively, you can process the entire flow yourself:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent.data?.let(::process)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
process(intent.data)
}
private fun process(uri: Uri?) {
uri?.getQueryParameters("sessionId")?.firstOrNull()?.let { sessionId ->
launch {
try {
val appKey = "[Your appKey]" // Get this value from the Keyri Developer Portal
val publicUserId = "public-User-Id" // publicUserId is optional
val payload = "Custom payload here"
val keyri = Keyri(this@MainActivity) // Be sure to import the SDK at the top of the file
keyri.initiateQrSession(appKey, sessionId, publicUserId)
.onSuccess { session ->
// You can optionally create a custom screen and pass the session ID there. We recommend this approach for large enterprises
val session =
keyri.initializeDefaultScreen(supportFragmentManager, session, payload)
.getOrThrow()
// In a real world example you’d wait for user confirmation first
session.confirm(payload) // or session.deny(payload)
}
// Process result
} catch (e: Throwable) {
Log.e("Keyri", "Authentication exception $e")
}
}
} ?: Log.e("Keyri", "Failed to process link")
}
Note: Keyri will set up the required /.well-known/assetlinks.json
JSON at your https://{yourSubdomain}.onekey.to
page as required by Android App Links handling. Details on this mechanism are described here: https://developer.android.com/training/app-links/verify-site-associations (opens in a new tab)
Option 2 - In-App Scanner
Add Scanner dependency to your module build.gradle file and sync project:
dependencies {
// ...
implementation("com.keyri:keyrisdk:$latestKeyriVersion")
implementation("com.keyri:scanner:$latestKeyriVersion")
}
Or with BOM:
dependencies {
// ...
implementation(platform("com.keyri:keyri-bom:$latestKeyriVersion"))
implementation("com.keyri:keyrisdk")
implementation("com.keyri:scanner")
}
Use AuthWithScannerActivity
built-in functionality to delegate authentication to SDK. You can use ActivityResult API
or onActivityResult
. Call easyKeyriAuth
and pass appKey
, payload
and optional publicUserId
:
private val easyKeyriAuthLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
val isSuccess = activityResult.resultCode == Activity.RESULT_OK
// Handle authentication result
// ...
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
binding.bAuthWithScanner.setOnClickListener {
easyKeyriAuth(
this,
easyKeyriAuthLauncher,
"[Your appKey]", // Get this value from the Keyri Developer Portal
"Custom payload here",
"public-User-Id", // publicUserId is optional
)
}
// Or with on activityResult:
binding.bAuthWithScanner.setOnClickListener {
// This will call an activity that will return a result
// Handle this result in onActivityResult function
easyKeyriAuth(
this,
REQUEST_CODE,
"[Your appKey]", // Get this value from the Keyri Developer Portal
"Custom payload here",
"public-User-Id", // publicUserId is optional
)
}
}
Or define custom scanner UI/UX. You can use Firebase ML Kit, ZXing, your own scanner, or any other equivalent. All you need to do is convert to URI, and then you're free to process the response the same way we did above (notice the process(uri)
function is exactly the same in both cases)
private fun scanQr() {
// Your scanner realization
// Get link from QR and process it:
process(uri)
}
private fun process(uri: Uri?) {
uri?.getQueryParameters("sessionId")?.firstOrNull()?.let { sessionId ->
launch {
try {
val appKey = "[Your appKey]" // Get this value from the Keyri Developer Portal
val publicUserId = "public-User-Id" // publicUserId is optional
val payload = "Custom payload here"
val keyri = Keyri(this@MainActivity) // Be sure to import the SDK at the top of the file
keyri.initiateQrSession(appKey, sessionId, publicUserId)
.onSuccess { session ->
// You can optionally create a custom screen and pass the session ID there. We recommend this approach for large enterprises
val session =
keyri.initializeDefaultScreen(supportFragmentManager, session, payload)
.getOrThrow()
// In a real world example you’d wait for user confirmation first
session.confirm(payload) // or session.deny(payload)
}
// Process result
} catch (e: Throwable) {
Log.e("Keyri", "Authentication exception $e")
}
}
} ?: Log.e("Keyri", "Failed to process link")
}
Jetpack Compose support
Add Keyri Compose dependency to your module build.gradle
file and sync project:
dependencies {
// ...
implementation("com.keyri:keyrisdk:$latestKeyriVersion")
implementation("com.keyri:compose:$latestKeyriVersion")
}
Or with BOM:
dependencies {
// ...
implementation(platform("com.keyri:keyri-bom:$latestKeyriVersion"))
implementation("com.keyri:keyrisdk")
implementation("com.keyri:compose")
}
Use ScannerPreview
for retrieving sessionId from scanned QR code. All you need here to provide is:
-
modifier: Modifier
- modifier object to change the appearance of the ScannerPreview. -
onScanResult: (Result<String>)
- callback for handling sessionId. -
onClose: () -> Unit
- callback for handling close button. -
isLoading: Boolean
- value to show progress bar.
After you receive Session
object you can create your own confirmation dialog or use
default: ConfirmationModalBottomSheet
. Provide next arguments:
-
modalBottomSheetState: ModalBottomSheetState
- state to to manage BottomSheet. -
coroutineScope: CoroutineScope
- coroutine scope for suspending calls inside BottomSheet. -
session: Session?
- session object to process (should not be null when modalBottomSheetState is shown). -
payload: String
- can be anything (session token or a stringified JSON containing multiple items. Can include things like publicUserId, timestamp, customSignedData and ECDSA signature). -
onResult: (Result<Boolean>)
- callback for handling result of user confirmation.
To handle deeplink with default confirmation screen you can use EasyKeyriAuth
composable:
val coroutineScope = rememberCoroutineScope()
val modalBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
skipHalfExpanded = true
)
EasyKeyriAuth(
modalBottomSheetState,
coroutineScope,
keyri,
sessionId,
"[Your appKey]", // Get this value from the Keyri Developer Portal
"Custom payload here",
"public-User-Id" // publicUserId is optional
) { result ->
// Process result
}
Interacting with the API
The following methods are available to interact with the Keyri SDK API, which can be used to craft your own custom flows and leverage the SDK in different ways:
-
suspend fun Keyri.initializeQrSession(appKey: String, sessionId: String, publicUserId: String?): Result<Session>
: call it after obtaining the sessionId from QR code or deep link. Returns result of Session object with Risk attributes (needed to show confirmation screen) or Throwable error -
suspend fun Keyri.initializeDefaultConfirmationScreen(fm: FragmentManager, session: Session. payload: String): Result<Boolean>
: to show Confirmation with default UI. Returns Boolean result or Throwable error. Also you can implement your custom Confirmation Screen, just inherit fromBaseConfirmationDialog.kt
-
suspend fun Session.confirm(payload: String): Result<Boolean>
: call this function if user confirmed the dialog. Returns Boolean authentication result or Throwable error -
suspend fun Session.deny(payload: String): Result<Boolean>
: call this function if user denied the dialog. Returns Boolean denial result or Throwable error -
fun Keyri.generateAssociationKey(publicUserId: String = "ANON"): String
: creates a persistent ECDSA keypair for the given publicUserId (example: email address) or default without arguments for anonymous and return Base64 string public key -
fun Keyri.generateUserSignature(publicUserId: String = "ANON", data: String): String
: returns an ECDSA signature of custom data for sign with the custom publicUserId's privateKey (or, if not provided, anonymous publicKey), customSignedData can be anything -
fun Keyri.listAssociationKey(): List<String>
: returns a list of names (publicUserIds) of " association keys" (public keys) -
fun Keyri.getAssociationKey(publicUserId: String = "ANON"): String
: returns Base64 public key for the specified publicUserId (or, if not provided, for anonymous publicKey) -
fun Keyri.processLink(fragmentManager: FragmentManager, uri: Uri, appKey: String, payload: String, publicUserId: String?): Result<Boolean>
: process flow with passed uri with showing default confirmation screen. Easiest way to process session from deeplink. Returns Boolean result of authentication or Throwable error -
fun easyKeyriAuth(content: Context, easyKeyriAuthLauncher: ActivityResultLauncher<Intent>, appKey: String, payload: String, publicUserId: String?)
: launches scanner activity with default confirmation screen for ActivityResultLauncher -
fun easyKeyriAuth(activity: Activity, requestCode: Int, appKey: String, payload: String, publicUserId: String?)
: launches scanner activity for result with default confirmation screen for onActivityResult -
@Composable fun EasyKeyriAuth(sheetState: ModalBottomSheetState, coroutineScope: CoroutineScope, keyri: Keyri, sessionId: String, appKey: String, payload: String, publicUserId: String?, result: (Result<Boolean>) -> Unit)
: handle process flow with passed scanned url and showing default confirmation screen. Easiest way to process session from deeplink -
@Composable fun ConfirmationModalBottomSheet(modalBottomSheetState: ModalBottomSheetState, coroutineScope: CoroutineScope, session: Session? = null, payload: String, onResult: (Result<Boolean>) -> Unit)
: to show Confirmation with default UI. Returns Boolean result or Throwable error -
@Composable fun ScannerPreview(modifier: Modifier = Modifier, onScanResult: (Result<String>) -> Unit = {}, onClose: () -> Unit = {}, isLoading: Boolean = false)
: default QR scanner implementation based on ML Kit. Returns result of scanning (string sessionId or error)
Payload can be anything (session token or a stringified JSON containing multiple items. Can include things like publicUserId, timestamp, customSignedData and ECDSA signature)
Session Object
The session object is returned on successful initializeQrSession
calls, and is used to handle presenting the situation to the end user and getting their confirmation to complete authentication. Below are some of the key properties and methods that can be triggered. If you are utilizing the built-in views, you are only responsible for calling the confirm/deny methods above
-
iPAddressMobile/Widget - The IP Address of both mobile device and web browser
-
riskAnalytics - if applicable
-
riskStatus - clear, warn or deny
-
riskFlagString - if RiskStatus is warn or deny, this string alerts the user to what is triggering the risk situation
-
geoData - Location data for both mobile and widget
-
mobile
-
city
-
country_code
-
-
browser
-
city
-
country_code
-
-
-
-
mobileTemplateResponse - Use this object to define confirmation screen UI
-
Session.confirm()
andSession.deny()
- see descriptions in Interacting with the API.
Disclaimer
We care deeply about the quality of our product and rigorously test every piece of functionality we offer. That said, every integration is different. Every app on the App Store has a different permutation of build settings, compiler flags, processor requirements, compatibility issues etc and it's impossible for us to cover all of those bases, so we strongly recommend thorough testing of your integration before shipping to production. Please feel free to file a bug or issue if you notice anything that seems wrong or strange on GitHub 🙂
https://github.com/Keyri-Co/keyri-android-whitelabel-sdk/issues (opens in a new tab)