To get started, you need to install dependencies for kotlinx-serialization, kotlin-cocoapods and Otpless Android & iOS dependencies. You can do this by adding following the below mentioned steps:
In your build.gradle.kts(:composeApp) file, add the following code:
Copy
plugins { alias(libs.plugins.kotlinCocoapods) kotlin("plugin.serialization") version "2.1.10"}
Adding Otpless SDK dependencies and kotlinx-serialization in your build.gradle.kts(:composeApp) file:
Copy
kotlin { sourceSets { androidMain.dependencies { implementation("io.github.otpless-tech:otpless-headless-sdk:0.2.0") } commonMain.dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") } } // Cocoapods configuration: This will create a cocoapod for your iOS app. // So, set the description that you would want in your Podspec file. cocoapods { homepage = "Your homepage name" summary = "Your cocoapods summary" version = "1.0" ios.deploymentTarget = "15.3" podfile = project.file("../iosApp/Podfile") framework { baseName = "composeApp" isStatic = true } // Add the Otpless SDK dependency to the iOS framework pod("OtplessBM/Core") { version = "1.1.3" extraOpts += listOf("-compiler-option", "-fmodules") } }}
Replace YOUR_APP_ID with your actual App
ID provided in
your OTPLESS dashboard.
Add Network Security Config inside your android/app/src/main/AndroidManifest.xml file into your <application> code block (Only required if you are using the SNA feature):
Replace YOUR_APP_ID with your actual App
ID provided in
your OTPLESS dashboard.
Add Network Security Config inside your android/app/src/main/AndroidManifest.xml file into your <application> code block (Only required if you are using the SNA feature):
Add the following code to handle deeplinks in your iOS app:
Copy
import OtplessBMvar body: some Scene { WindowGroup { ContentView() // Check for deep link .onOpenURL(perform: { url in if Otpless.shared.isOtplessDeeplink(url: url.absoluteURL) { Otpless.shared.handleDeeplink(url: url.absoluteURL) } }) }}
Step 3: Create expect function declarations in commonMain
In your commonMain source set, create a new file named OtplessAuthHandler.kt and add the following code:
Copy
expect fun initializeOtpless(appId: String, onOtplessResponse: (String) -> Unit, loginUri: String?)expect fun start(otplessCMPRequest: OtplessCMPRequest)expect fun cleanup()data class OtplessCMPRequest( val phoneNumber: String? = null, val countryCode: String? = null, val email: String? = null, val otp: String? = null, val otpExpiry: String? = null, val otpLength: String? = null, val deliveryChannel: String? = null, val oAuthChannel: String? = null)
In your LoginScreen.kt file, initialize the Otpless SDK. Make sure to initialize the SDK in DisposableEffect block to free the resources consumed by Otpless once your LoginScreen.kt is destroyed.
Copy
DisposableEffect(key1 = Unit) { // Use key1 as Unit so that the SDK is not re-initialized on recomposition initializeOtpless( appId = "YOUR_APPID", onOtplessResponse = { otplessResponseString -> response = otplessResponseString + "\n\n" + response handleOtplessResponse(otplessResponseString, onOTPAutoRead = { otp = it }) }, loginUri = null ) onDispose { cleanup() }}
To handle the Otpless response, create a function named handleOtplessResponse in your LoginScreen.kt file. This function will handle the response received from the Otpless SDK.
Copy
import kotlinx.serialization.json.Jsonimport kotlinx.serialization.json.JsonObjectimport kotlinx.serialization.json.contentOrNullimport kotlinx.serialization.json.intOrNullimport kotlinx.serialization.json.jsonObjectimport kotlinx.serialization.json.jsonPrimitivefun handleOtplessResponse(responseJsonString: String, onOTPAutoRead: (String) -> Unit) { val parsedElement = Json.parseToJsonElement(responseJsonString).jsonObject val responseType = parsedElement["responseType"]?.jsonPrimitive?.contentOrNull val statusCode = parsedElement["statusCode"]?.jsonPrimitive?.intOrNull val responseJsonObject = parsedElement["response"]?.jsonObject when (responseType) { "SDK_READY" -> { // SDK initialized successfully, enable continue button log("SDK is ready!") } "FAILED" -> { log("SDK initialization failed!") if (statusCode == 5003) { // SDK initialization failed, retry initializing } else { // General failure handling } } "INITIATE" -> { if (statusCode != 200) { if (getPlatformName().lowercase() == "android") { handleInitiateErrorAndroid(responseJsonObject) } else { handleInitiateErrorIos(responseJsonObject) } } else { val authType = responseJsonObject?.get("authType")?.jsonPrimitive?.contentOrNull when (authType) { "OTP" -> { log("Authentication started using OTP") // Take user to OTP verification screen } "SILENT_AUTH" -> { log("Authentication started using Silent Auth") // Handle Silent Authentication initiation (show loading) } } } } "OTP_AUTO_READ" -> { // Only applicable in Android val otp = responseJsonObject?.get("otp")?.jsonPrimitive?.contentOrNull if (!otp.isNullOrBlank()) { // Autofill OTP in your text field onOTPAutoRead(otp) } } "VERIFY" -> { val authType = responseJsonObject?.get("authType")?.jsonPrimitive?.contentOrNull if (authType == "SILENT_AUTH") { if (statusCode == 9106) { // Silent Auth + fallback failed → gracefully exit auth flow log("SNA + fallback failed!") } else { log("SNA failed, trying fallback!") } } else { if (getPlatformName().lowercase() == "android") { handleVerifyErrorAndroid(responseJsonObject) } else { handleVerifyErrorIos(responseJsonObject) } } } "DELIVERY_STATUS" -> { val authType = responseJsonObject?.get("authType")?.jsonPrimitive?.contentOrNull val deliveryChannel = responseJsonObject?.get("deliveryChannel")?.jsonPrimitive?.contentOrNull // Handle delivery status (authType, deliveryChannel) log("DELIVERY_STATUS: authType: $authType \b deliveryChannel: $deliveryChannel") } "FALLBACK_TRIGGERED" -> { val newDeliveryChannel = responseJsonObject?.get("deliveryChannel")?.jsonPrimitive?.contentOrNull // Handle fallback deliveryChannel log("Fallback triggered, new delivery channel: $newDeliveryChannel") } "ONETAP" -> { val data = responseJsonObject?.get("data")?.jsonObject val token = data?.get("token")?.jsonPrimitive?.contentOrNull if (!token.isNullOrBlank()) { // Process token and proceed log("Token: $token") } } }}
To verify the OTP entered by the user, use the verify method with the necessary parameters. Verifying OTP is required only in case of OTP authentication. No need to verify OTP in case of MAGICLINK.
To verify the OTP entered by the user, use the verify method with the necessary parameters. Verifying OTP is required only in case of OTP authentication. No need to verify OTP in case of MAGICLINK.
To verify the OTP entered by the user, use the verify method with the necessary parameters. Verifying OTP is required only in case of OTP authentication. No need to verify OTP in case of MAGICLINK.
Copy
val otplessCMPRequest = OtplessCMPRequest( otp = otp, email = email)start(otplessCMPRequest)
OAuth Authentication 🔑
OAuth allows users to authenticate using third-party services like Google, GitHub, or WhatsApp. Instead of entering credentials manually, users can log in using their existing accounts, streamlining the authentication process.
This is the list of channels you can use for OAuth login:
WHATSAPP
APPLE
GMAIL
TWITTER
DISCORD
SLACK
FACEBOOK
LINKEDIN
MICROSOFT
LINE
LINEAR
NOTION
TWITCH
GITHUB
BITBUCKET
ATLASSIAN
GITLAB
Copy
val otplessCMPRequest = OtplessCMPRequest( oAuthChannel = "CHANNEL_NAME")// Make sure that the CHANNEL_NAME is in uppercasestart(otplessCMPRequest)
private fun handleInitiateErrorIos(responseJsonObject: JsonObject?) { val errorCode = responseJsonObject?.get("errorCode")?.jsonPrimitive?.contentOrNull val errorMessage = responseJsonObject?.get("errorMessage")?.jsonPrimitive?.contentOrNull ?: "Unknown error" when (errorCode) { "7101" -> log("iOS OTPless Error: Invalid parameters values or missing parameters - $errorMessage") "7102" -> log("iOS OTPless Error: Invalid phone number - $errorMessage") "7103" -> log("iOS OTPless Error: Invalid phone number delivery channel - $errorMessage") "7104" -> log("iOS OTPless Error: Invalid email - $errorMessage") "7105" -> log("iOS OTPless Error: Invalid email channel - $errorMessage") "7106" -> log("iOS OTPless Error: Invalid phone number or email - $errorMessage") "7113" -> log("iOS OTPless Error: Invalid expiry - $errorMessage") "7116" -> log("iOS OTPless Error: OTP Length is invalid (4 or 6 only allowed) - $errorMessage") "7121" -> log("iOS OTPless Error: Invalid app hash - $errorMessage") "4000" -> log("iOS OTPless Error: Invalid request values - $errorMessage") "4003" -> log("iOS OTPless Error: Incorrect request channel - $errorMessage") "401", "7025" -> log("iOS OTPless Error: Unauthorized request or country not enabled - $errorMessage") // Rate limiting errors "7020", "7022", "7023", "7024" -> log("iOS OTPless Error: Rate limiting error (Too many requests) - $errorMessage") // Feature not supported on older iOS versions "5900" -> log("iOS OTPless Error: The feature is not supported because it requires a newer iOS version - ${errorMessage.ifBlank { "The requested feature is only available on newer iOS versions." }}") // Network-related errors "9100", "9101", "9102", "9103", "9104", "9105", "9110" -> log( "iOS OTPless Error: Network error - " + when (errorCode) { "9100" -> errorMessage.ifBlank { "The request took too long to complete and was aborted." } "9101" -> errorMessage.ifBlank { "The connection was interrupted before the request could complete." } "9102" -> errorMessage.ifBlank { "The domain name could not be resolved, possibly due to network issues." } "9103" -> errorMessage.ifBlank { "The server is unreachable, possibly due to downtime or incorrect configurations." } "9104" -> errorMessage.ifBlank { "The device is not connected to the internet." } "9105" -> errorMessage.ifBlank { "A secure connection could not be established due to SSL/TLS issues." } "9110" -> errorMessage.ifBlank { "The authentication request was manually canceled by the user or the system." } else -> errorMessage } ) else -> log("iOS OTPless Error: $errorMessage") }}