FIDO2
Introduction
This guide explains how to implement the FIDO2 registration and authentication workflow on Android applications. You’ll find a detailed introduction about FIDO2 in our FIDO2 guide.
The following code samples and screenshots are issued from our example application using our SDK. Feel free to check out the Github project. |
FIDO2 Prerequisites
To successfully enable FIDO2:
-
Enable the WebAuthn feature on your ReachFive account.
You may need to contact support to have this enabled if you do not see it on your ReachFive Console. -
From your ReachFive console:
-
Navigate to Settings > WebAuthn.
-
Configure the following:
-
Application name
: the name of the application that is displayed to the user during the FIDO2 Registration or Authentication process. There are no particular restrictions on what you can use here. -
Allowed origins
: the list of URLs that are allowed to make calls to the API endpoints provided by ReachFive to perform the FIDO2 Registration or Authentication process.
-
-
- You also need
-
-
An Android device with a fingerprint sensor or a configured screen lock.
-
Android OS 7.0 or later.
Make sure to register a fingerprint (or screenlock). -
To install our SDK Core which exposes all of the FIDO2 methods.
-
To include the play-services-fido library as a dependency. To do this, go to your
app
folder and add the following to thebuild.gradle
file underdependencies
.dependencies { ... implementation "com.google.android.gms:play-services-fido:18.1.0" ... }
-
Initialize
Digital Asset Links
You need to host a Digital Asset Links JSON file on https://<your_domain>/.well-known/assetlinks.json
to allow the application to use FIDO2 APIs to register and sign credentials for that domain.
In our application example, we are:
-
Creating a Firebase Hosting project to host the digital asset link.
-
Setting the
domain
to<your_domain>
. -
Hosting the
assetlinks.json
athttps://<your_domain>/.well-known/assetlinks.json
.
[
{
"relation": ["delegate_permission/common.handle_all_urls","delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "com.reach5.identity.sdk.demo", (1)
"sha256_cert_fingerprints": [ (2)
"AF:C8:F2:4C:AF:46:AA:5B:74:7B:8D:47:4D:82:A6:D1:FF:98:BF:A6:16:D0:D0:82:E8:9A:D0:8C:13:EB:B9:CE"
]
}
}
]
In the assetlinks.json
file sample above:
-
package_name
matches theapplicationId
in thebuild.gradle
file. -
sha256_cert_fingerprints
matches the fingerprint of the signing key.
Refer to Android Studio’s Generate and register an upload certificate page to generate a public certificate. |
Allowed origins
In the ReachFive Console, under
you must allow:-
the domain on which the Digital Asset Links file is hosted
-
the Facet ID
The Facet ID is the platform-specific identifier (URI). It matches the format You can compute the Facet ID with the command below:
|
Signup
The user requests to register a new account for the first time. Users don’t need to provide a password, but instead provide an identifier like an email or mobile number as well as some personal data.
The data they provide in the signup process is reflected in the profile option in the signupWithWebAuthn method.
|
- Subsection
-
Jump to the signup process
Here is a snapshot of the signup view displayed to the user.
Signup process
Signup is a two-step process.
Each tab below shows the code needed for each step. |
-
The signupWithWebAuthn method initiates the FIDO2 signup process to retrieve a randomly generated challenge, the Relying Party information, and the user information from the ReachFive server. The options are then passed to the FIDO2 API which returns an Android Intent to open a fingerprint dialog and generates a new credential.
-
The onLoginActivityResult method is called after the UI successfully generates a new credential. It sends the resulting data (which contains the information about this new credential) back to the ReachFive server and finalizes the FIDO2 signup process by returning the user’s authentication token.
package com.reach5.identity.sdk.demo
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.reach5.identity.sdk.core.ReachFive
import com.reach5.identity.sdk.core.models.requests.ProfileWebAuthnSignupRequest
import kotlinx.android.synthetic.main.webauthn_signup.*
// The activity controlling the view
class MainActivity : AppCompatActivity() {
// The ReachFive client
private lateinit var reach5: ReachFive
// The WebAuthn identifier of the user
private lateinit var webAuthnId: String
companion object {
// The domain of the origin call (it can be stored in an env file)
const val ORIGIN = "https://dev-sandbox-268508.web.app"
// The code of the FIDO2 signup request
const val WEBAUTHN_SIGNUP_REQUEST_CODE = 3
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Define the action on the click of the "Signup" button
signupWithWebAuthn.setOnClickListener {
// Launch a pending FIDO2 task for signup
this.reach5.signupWithWebAuthn(
profile = ProfileWebAuthnSignupRequest(
email = signupWebAuthnEmail.text.toString(),
givenName = signupWebAuthnGivenName.text.toString(),
familyName = signupWebAuthnFamilyName.text.toString()
),
origin = ORIGIN,
friendlyName = signupWebAuthnNewFriendlyName.text.toString(),
signupRequestCode = WEBAUTHN_SIGNUP_REQUEST_CODE,
successWithWebAuthnId = { this.webAuthnId = it },
failure = {
Log.d("ReachFive","Unable to signup with FIDO2: $it")
}
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// Method A
// Tip: nothing happens if the request code does not concern ReachFive
client.onLoginActivityResult(
requestCode = requestCode,
resultCode = resultCode,
intent = data,
success = { handleLoginSuccess(it) },
failure = { reachFiveError ->
Log.e(TAG, "Login error!", reachFiveError)
},
activity = this
)
// Method B
val handler: ActivityResultHandler? = reach5.resolveResultHandler(requestCode, resultCode, data)
when (handler) {
is LoginResultHandler -> handler.handle(
success = { handleLoginSuccess(it) },
failure = { reachfiveError ->
Log.e(TAG, "Login error!", reachfiveError)
},
activity = this
)
else -> { /* Not a login callback, or not ReachFive */ }
}
}
Authentication
Once the user has a credential registered on the server and the device, they can use it to login easily with their identifier.
- Subsection
-
Jump to the authentication process
Here is a snapshot of the login view displayed to the user.
Authentication process
Authentication is a two-step process.
Each tab below shows the code needed for each step. |
-
The loginWithWebAuthn method initiates the FIDO2 authentication process to retrieve the list of previously registered credentials and a challenge string from the server. This information is passed to the FIDO2 API which searches for a credential that matches the Relying Party ID and creates an Android Intent to open the fingerprint dialog for the user to consent for authentication.
-
The onLoginActivityResult method is called after the UI successfully retrieves the existing credential. It returns the operation result to the ReachFive server and finalizes the FIDO2 authentication process by returning the user’s authentication token.
package com.reach5.identity.sdk.demo
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.reach5.identity.sdk.core.ReachFive
import com.reach5.identity.sdk.core.models.requests.webAuthn.WebAuthnLoginRequest
import kotlinx.android.synthetic.main.webauthn_login.*
// The activity controlling the view
class MainActivity : AppCompatActivity() {
// The ReachFive client
private lateinit var reach5: ReachFive
// The scope assigned to the user
private val scope = setOf("openid", "email", "profile", "phone_number", "offline_access", "events", "full_write")
companion object {
// The domain of the origin call (it can be stored in an env file)
const val ORIGIN = "https://dev-sandbox-268508.web.app"
// The code of the FIDO2 authentication request
const val WEBAUTHN_LOGIN_REQUEST_CODE = 2
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Define the action on the click of the "Login" button
loginWithWebAuthn.setOnClickListener {
val email: String = webAuthnEmail.text.toString()
// Build the request according to the identifier provided by the user
val webAuthnLoginRequest: WebAuthnLoginRequest = if (email.isNotEmpty()) {
WebAuthnLoginRequest.EmailWebAuthnLoginRequest(
origin = ORIGIN,
email = email,
scope = scope
)
} else {
WebAuthnLoginRequest.PhoneNumberWebAuthnLoginRequest(
origin = ORIGIN,
phoneNumber = webAuthnPhoneNumber.text.toString(),
scope = scope
)
}
// Launch a pending FIDO2 task for authentication
this.reach5.loginWithWebAuthn(
loginRequest = webAuthnLoginRequest,
loginRequestCode = WEBAUTHN_LOGIN_REQUEST_CODE,
failure = {
Log.d("ReachFive","Unable to login with FIDO2: $it")
}
)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// Method A
// Tip: nothing happens if the request code does not concern ReachFive
client.onLoginActivityResult(
requestCode = requestCode,
resultCode = resultCode,
intent = data,
success = { handleLoginSuccess(it) },
failure = { reachFiveError ->
Log.e(TAG, "Login error!", reachFiveError)
},
activity = this
)
// Method B
val handler: ActivityResultHandler? = reach5.resolveResultHandler(requestCode, resultCode, data)
when (handler) {
is LoginResultHandler -> handler.handle(
success = { handleLoginSuccess(it) },
failure = { reachfiveError ->
Log.e(TAG, "Login error!", reachfiveError)
},
activity = this
)
else -> { /* Not a login callback, or not ReachFive */ }
}
}
Manage devices
Once the user is authenticated, they can perform several actions to manage the registered devices such as:
Here is a snapshot of the view displayed to the user after a successful login.
Register a new device
Registration is a two-step process.
Each tab below shows the code needed for each step. |
-
The addNewWebAuthnDevice method initiates the FIDO2 registration process to retrieve a randomly generated challenge, the Relying Party information, and the user information from the ReachFive server. The options are then passed to the FIDO2 API which returns an Android Intent to open a fingerprint dialog and generates a new credential.
A user cannot register several credentials on the same device. -
The onAddNewWebAuthnDeviceResult method is called after the UI successfully generates a new credential. It sends the resulting data (which contains the information about this new credential) back to the ReachFive server and completes the FIDO2 registration process.
package com.reach5.identity.sdk.demo
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.reach5.identity.sdk.core.ReachFive
import com.reach5.identity.sdk.core.models.responses.AuthToken
import kotlinx.android.synthetic.main.webauthn_devices.*
// The activity controlling the view
class AuthenticatedActivity : AppCompatActivity() {
// The ReachFive client
private lateinit var reach5: ReachFive
// The authentication token retrieved after the user has logged-in
private lateinit var authToken: AuthToken
companion object {
// The domain of the origin call (it can be stored in an env file)
const val ORIGIN = "https://dev-sandbox-268508.web.app"
// The code of the FIDO2 registration request
const val WEBAUTHN_REGISTER_REQUEST_CODE = 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_authenticated)
// Fetch the authentication token from another activity
this.authToken = intent.getParcelableExtra("AUTH_TOKEN")
// Define the action on the click of the "Add new FIDO2 device" button
addNewDevice.setOnClickListener {
// Launch a pending FIDO2 task for registration
this.reach5.addNewWebAuthnDevice(
authToken = this.authToken,
origin = ORIGIN,
friendlyName = newFriendlyName.text.trim().toString(),
registerRequestCode = WEBAUTHN_REGISTER_REQUEST_CODE,
failure = {
Log.d("ReachFive", "Unable to add a new FIDO2 device: $it")
}
)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Method A, using SDK helper:
val handler = reach5.resolveResultHandler(requestCode, resultCode, data)
if (handler is WebAuthnDeviceAddResult) {
handler.handle(
success = {
showToast("New FIDO2 device registered")
refreshDevicesDisplayed()
},
failure = {
Log.d(TAG, "onAddNewWebAuthnDeviceResult error=$it")
showErrorToast(it)
}
)
}
// Method B, directly calling method. Nothing happens if requestCode does not concern SDK:
reach5.onAddNewWebAuthnDeviceResult(
requestCode,
data,
success = {
showToast("New FIDO2 device registered")
refreshDevicesDisplayed()
},
failure = {
Log.d(TAG, "onAddNewWebAuthnDeviceResult error=$it")
showErrorToast(it)
}
)
}
Here are some snapshots of the FIDO2 dialogs displayed to the user to save a fingerprint.
Other actions
The tabs below instruct you on listing and removing devices.
Use the listWebAuthnDevices method to list the registered FIDO2 devices.
package com.reach5.identity.sdk.demo
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.reach5.identity.sdk.core.ReachFive
import com.reach5.identity.sdk.core.models.responses.AuthToken
import com.reach5.identity.sdk.core.models.responses.webAuthn.DeviceCredential
// The activity controlling the view
class AuthenticatedActivity : AppCompatActivity() {
// The ReachFive client already instantiated in another activity
private lateinit var reach5: ReachFive
// The authentication token retrieved after the user has logged-in
private lateinit var authToken: AuthToken
// The list of the FIDO2 devices of the user
private lateinit var devicesDisplayed: List<DeviceCredential>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_authenticated)
/*...*/
// Fetch the authentication token from another activity
this.authToken = intent.getParcelableExtra("AUTH_TOKEN")
// Initialize the list of devices
this.devicesDisplayed = listOf()
// Fetch the registered devices of the user from Reachfive server
this.reach5.listWebAuthnDevices(
authToken = authToken,
success = {
// Refresh the list of displayed devices
this.devicesDisplayed = it
},
failure = {
Log.d("ReachFive","Unable to list the FIDO2 devices: $it")
}
)
}
}
+
Use the removeWebAuthnDevice method to delete a registered FIDO2 device.
package com.reach5.identity.sdk.demo
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.reach5.identity.sdk.core.ReachFive
import com.reach5.identity.sdk.core.models.responses.AuthToken
import com.reach5.identity.sdk.core.models.responses.webAuthn.DeviceCredential
// The activity controlling the view
class AuthenticatedActivity : AppCompatActivity() {
// The ReachFive client already instantiated in another activity
private lateinit var reach5: ReachFive
// The authentication token retrieved after the user has logged-in
private lateinit var authToken: AuthToken
// The list of the FIDO2 devices of the user
private lateinit var devicesDisplayed: List<DeviceCredential>
/*...*/
private fun removeDevice(position: Int) {
// The selected device
val device = this.devicesDisplayed[position]
// Delete the selected registered device from the ReachFive server
reach5.removeWebAuthnDevice(
authToken = authToken,
deviceId = device.id,
success = {
Log.d("ReachFive", "The FIDO2 device '${device.friendlyName}' is removed")
},
failure = {
Log.d("ReachFive", "Unable to remove the FIDO2 device '${device.friendlyName}': $it")
}
)
}
}
+