Passwordless Mobile Authentication with Android
Tags: android, kotlin, tutorial, security
Published: 2021-08-27 13:27:38

Before we begin, let's discuss why you would want to use [**tru.ID** PhoneCheck](https://developer.tru.id/docs/phone-check) in your onboarding workflows.

20-30% of signup attempts fail on mobile due to incomplete authentication; an SMS is delayed or not delivered at all, an email ends up in spam or is undelivered, a voice call fails, or the user makes a mistake as they navigate the poor UX of a signup flow which uses current legacy authentication options. **tru.ID** PhoneCheck offers a new way to verify a user, reducing UX friction and improving onboarding conversion rates.

It works by confirming the ownership of a mobile phone number by verifying the possession of an active SIM card with the same number. A mobile data session is created to a unique **Check URL** for the purpose of this verification. **tru.ID** then resolves a match between the phone number being verified and the phone number that the mobile network operator identifies as the owner of the mobile data session.

<WorkingApp />

If you just want to dive into the finished code for this tutorial, you can find it on [Github](https://github.com/tru-ID/android-passwordless-auth).

## Before you begin

Before you begin you'll need the following:

- Android capable IDE ([Android Studio](https://developer.android.com/studio) recommended)
- An Android phone with mobile data connection
- [Node.js](https://nodejs.org/)

## Getting Started

Clone the `starter-files` branch via:

```bash
git clone -b starter-files --single-branch https://github.com/tru-ID/passwordless-auth-android.git
```

Create a [**tru.ID**](https://tru.id) account and you'll end up on the Console containing CLI setup instructions.

Install the **tru.ID** CLI via:

```bash
npm i -g @tru_id/cli
```

Input your tru.ID credentials, which can be found within the [tru.ID console](https://developer.tru.id/console).

Install the **tru.ID** CLI [development plugin server](https://github.com/tru-ID/cli-plugin-dev-server).

Create a new **tru.ID** project within the root directory via:

```bash
cd passwordless-auth-android && tru projects:create --project-dir .
```

Run the development server, pointing it to the directory containing the newly created project configuration. This will also open up a localtunnel to your development server, making it publicly accessible to the Internet so that your mobile phone can access it when only connected to mobile data.

```bash
tru server -t --project-dir .
```

## Starting the project

Open the project up in your Android capable IDE, connect your phone to your computer so it can be used for running the Android project, and run the application from your IDE.

The project should look like this:

![A screenshot of an Android device with the onboarding screen for the demo application. The majority of the screen is taken up by a hand drawn safe outline. Followed by the text "Welcome" and then a purple button with the text "Get Started"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewzvqk28pqbee2rti2hx.png)

The project's onboarding workflow includes four screens: _splash_, _get started_, _sign up_ and _signed up_.

Click the **Get Started** button to navigate to the `signupFragment` UI. It looks like this:

![Screenshot of an Android phone, with the demo application running showing the tru.ID logo, followed by the label "Enter your number and sign up", then a text field with the placeholder "Phone Number", lastly there's a button at the bottom with the label "Sign up"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nt5f043dxa35kkpndti0.png)

## Creating a PhoneCheck

The first step is to create the PhoneCheck. In order to do this, we need to send a `POST` request with the user's phone number to `/phone-check`.

We then get back a `check_url` and `check_id` which we will use for subsequent calls.

First, add the following dependencies for data fetching in `app/build.gradle`:

```kotlin
dependencies {
     // retrofit and converter
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}
```

Next, create a folder (package) in `src/main/java/com/example/tru_phonecheck` called `api` and within that create a subfolder (package) called `data`.

Within `data`, create a class called `PhoneCheck.kt` and add the following code:

```kotlin
package com.example.tru_phonecheck.api.data

import com.google.gson.annotations.SerializedName

data class PhoneCheck(  @SerializedName("check_url")
                        val check_url: String,
                        @SerializedName("check_id")
                        val check_id: String) {
}

data class PhoneCheckPost (
    @SerializedName("phone_number")
    val phone_number: String
)
```

Here we have two data model classes: `PhoneCheck`, whose constructor accepts the values of the response, and `PhoneCheckPost`, whose constructor accepts the user's phone number.

We also use `@SerializedName` to ensure the values match the expected input / response.

Next, navigate back to the `api` folder and create another folder (package) named `retrofit`. Within it, create an interface named `RetrofitService` and replace its contents with the following:

```kotlin
package com.example.tru_phonecheck.api.retrofit

import com.example.tru_phonecheck.api.data.PhoneCheck
import com.example.tru_phonecheck.api.data.PhoneCheckPost
import retrofit2.Response
import retrofit2.http.*

interface RetrofitService {
    @Headers("Content-Type: application/json")
    @POST("/phone-check")
    suspend fun createPhoneCheck(@Body user: PhoneCheckPost): Response<PhoneCheck>
    companion object {
        // set up base_url in the format https://{subdomain}.loca.lt gotten from localTunnel URL
        const val base_url = "https://{subdomain}.loca.lt"
    }
}
```

Here we make use of the model we created to make the `POST` request. We also create a `companion object`, which allows us to access `base_url` as an object (i.e. `RetrofitService.base_url`).

Remember to swap out the placeholder URL in `base_url` with the localTunnel URL.

We now need to grab the user's phone number when the user touches the button and initiate the `POST` request.

First, head over to `src/main/java/com/example/tru_phonecheck/fragments/onboarding/screens/signupFragment.kt` and add a function that disables the UI when the button is touched:

```kotlin
  private fun setUIStatus (button: Button?, input: EditText, enabled: Boolean){
      activity?.runOnUiThread {
              button?.isClickable = enabled
              button?.isEnabled = enabled
              input.isEnabled = enabled
      }
  }
```

Next, add the following to create the Retrofit service:

```kotlin
//retrofit setup
private fun rf(): RetrofitService {
    return  Retrofit.Builder().baseUrl(RetrofitService.base_url).addConverterFactory(GsonConverterFactory.create()).build().create(RetrofitService::class.java)
}
```

Finally, add the following before `return view` in the `onCreateView` function:

```kotlin
...
view.submitHandler.setOnClickListener {
    // get phone number
    val phoneNumber = phoneNumberInput.text.toString()
    Log.d("phone number is", phoneNumber)

    // close virtual keyboard
    phoneNumberInput.onEditorAction(EditorInfo.IME_ACTION_DONE)

    // if it's a valid phone number begin createPhoneCheck
    if(!isPhoneNumberFormatValid(phoneNumber)) {
        Snackbar.make(container as View, "Invalid Phone Number", Snackbar.LENGTH_LONG).show()
    } else {
        println("valid number")

        // disable the UI
        setUIStatus(submitHandler, phoneNumberInput, false)

        CoroutineScope(Dispatchers.IO).launch {
          try {
              val response = rf().createPhoneCheck(PhoneCheckPost(phoneNumber))

              if(response.isSuccessful && response.body() != null){
                  val phoneCheck = response.body() as PhoneCheck

                  // open checkURL

                  // get PhoneCheck result
              }
          } catch(e: Throwable){
              Snackbar.make(container as View, e.message!!, Snackbar.LENGTH_SHORT).show()
          }

          // enable the UI
          setUIStatus(submitHandler, phoneNumberInput, true)
        }
    }
}
```

Here we attach a click listener to the button with the ID `submitHandler`. When the button is clicked, we grab the phone number and check if it is valid via a utility function located in `src/main/java/com/example/tru_phonecheck/utils/PhoneNumberUtil.kt`. If it is a valid number, we disable the UI via `setUIStatus` and make a network request to create the PhoneCheck using the`rf().createPhoneCheck` function.

![A screenshot of a mobile phone running the demo app. The app has a white background, tru.ID logo, below this is a label with the text "Enter your number and sign up", followed by a text box with a phone number, and a disabled button.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f35awi7uftwdm11flvsg.png)

## Opening the Check URL

The next stage is to open the Check URL returned to us. To do this, we need to use the [**tru.ID** Android SDK](https://github.com/tru-ID/tru-sdk-android). The SDK forces the Check URL network request to go over the mobile data connection so that the mobile network operator and **tru.ID** can verify the phone number.

Head back to `build.gradle` and add the following dependency:

```kotlin
dependencies {
  ...
    // tru.ID Android package
    implementation 'id.tru.sdk:tru-sdk-android:0.0.3'
}
```

Next, we need to initialize the SDK on startup. To do that, head over to `MainActivity.kt` and update it to the following:

```kotlin
package com.example.tru_phonecheck

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import id.tru.sdk.TruSDK

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        // initialize Tru SDK
        TruSDK.initializeSdk(applicationContext)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        supportActionBar?.hide()
    }
}
```

Head back to `SignupFragment.kt` and add the following above `onCreateView`:

```kotlin
private val truSDK = TruSDK.getInstance()
```

Use the SDK to open the Check URL in the `setOnClickListener` function:

```kotlin
if(response.isSuccessful && response.body() != null){
    val phoneCheck = response.body() as PhoneCheck

    // open checkURL
    truSDK.openCheckUrl(phoneCheck.check_url)

    // get PhoneCheck result
}
```

We have now successfully opened the Check URL.

## Getting the PhoneCheck Response

The last thing we need to do is get the PhoneCheck response.

Head over to `PhoneCheck.kt` and update its contents:

```kotlin
package com.example.tru_phonecheck.api.data

import com.google.gson.annotations.SerializedName

data class PhoneCheck(  @SerializedName("check_url")
                        val check_url: String,
                        @SerializedName("check_id")
                        val check_id: String) {
}

data class PhoneCheckPost (
    @SerializedName("phone_number")
    val phone_number: String
)

data class PhoneCheckResponse(
    @SerializedName("check_id")
    val check_id: String,
    @SerializedName("match")
    val match: Boolean
)
```

We added a new model, `PhoneCheckResponse`, which contains two fields: `check_id` and `match`. The latter is used to verify the phone number.

Next, head over to `RetrofitService.kt` and update the code support getting the PhoneCheck resource:

```kotlin
@GET("/phone-check")
suspend fun getPhoneCheck(@Query("check_id") checkId: String): Response<PhoneCheckResponse>
```

Here we added a `GET` function to `/phone-check?check_id={check_id}` where we pass the `check_id` value dynamically when it is called.

We also map the model type `PhoneCheckResponse` to the Response object we're expecting.

Let's now head over to `SignupFragment.kt` and add the functionality to get the PhoneCheck result:

```kotlin
if(response.isSuccessful && response.body() != null){
    val phoneCheck = response.body() as PhoneCheck

    // open checkURL
    truSDK.openCheckUrl(phoneCheck.check_url)

    // get PhoneCheck result
    val response = rf().getPhoneCheck(phoneCheck.check_id)

    if(response.isSuccessful && response.body() != null){
        val phoneCheckResponse = response.body() as PhoneCheckResponse

        // update UI with phoneCheckResponse
        if(phoneCheckResponse.match){
            findNavController().navigate(R.id.action_signupFragment_to_signedUpFragment)

        } else {
            Snackbar.make(container as View, "Registration Failed", Snackbar.LENGTH_LONG).show()
        }
    }
    else {
        Snackbar.make(container as View, "An unexpected problem occurred", Snackbar.LENGTH_LONG).show()
    }
}
```

Finally, we get the result of the PhoneCheck using the `phoneCheck.check_id` we previously got with the `PhoneCheck` response. If the phone number has been verified, we navigate the user to the `signedUpFragment` view. If the verification failed (`phoneCheckResponse.match` is `false`) we render a "failure" toast.

The `signedUpFragment` UI looks as follows:

![Screenshot of the Android application, showing a successful authentication.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqjikv6x7rnjpn6lqa47.png)

That's it!

## Wrapping Up

With everything in place, we now have a seamless signup onboarding flow with minimal UX friction, resulting in no user drop-offs.

There you have it: you’ve successfully integrated **tru.ID** PhoneCheck into your Android onboarding workflow.