With support for passkeys, federated sign-in, and third-party authentication providers, Credential Manager is the recommended API for authentication on Android that provides a secure and convenient environment that allows users to sync and manage their credentials. For developers that use local FIDO2 credentials, you should update your app to support passkey authentication by integrating with the Credential Manager API. This document describes how to migrate your project from FIDO2 to Credential Manager.
Reasons to migrate from FIDO2 to Credential Manager
In most cases, you should migrate your Android app's authentication provider to Credential Manager. The reasons to migrate to Credential Manager include:
- Passkey support: Credential Manager supports passkeys, a new, passwordless authentication mechanism that is more secure and easier to use than passwords.
- Multiple sign-in methods: Credential Manager supports multiple sign-in methods, including passwords, passkeys, and federated sign-in methods. This makes it easier for users to authenticate to your app, regardless of their preferred authentication method.
- Third party credential provider support: On Android 14 and higher, Credential Manager supports multiple third party credential providers. This means your users can use their existing credentials from other providers to sign in to your app.
- Consistent user experience: Credential Manager provides a more consistent user experience for authentication across apps and sign-in mechanisms. This makes it easier for users to understand and use your app's authentication flow.
To begin migration from FIDO2 to Credential Manager, follow the steps below.
Update dependencies
Update the Kotlin plugin in your project's build.gradle to version 1.8.10 or higher.
plugins { //… id 'org.jetbrains.kotlin.android' version '1.8.10' apply false //… }
In your project's build.gradle, update your dependencies to use Credential Manager and Play Services Authentication.
dependencies { // ... // Credential Manager: implementation 'androidx.credentials:credentials:<latest-version>' // Play Services Authentication: // Optional - needed for credentials support from play services, for devices running // Android 13 and below: implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>' // ... }
Replace FIDO initialization with Credential Manager initialization. Add this declaration in the class you use for passkey creation and sign in methods:
val credMan = CredentialManager.create(context)
Create passkeys
You'll need to create a new passkey, associate it with a user's account, and store the passkey's public key on your server before the user can sign in with it. Set up your app with this ability by updating the register function calls.
To obtain the necessary parameters that are sent to the
createCredential()
method during passkey creation, addname("residentKey").value("required")
as described in the WebAuthn specification) to yourregisterRequest()
server call.suspend fun registerRequest(sessionId: String ... { // ... .method("POST", jsonRequestBody { name("attestation").value("none") name("authenticatorSelection").objectValue { name("residentKey").value("required") } }).build() // ... }
Set the
return
type forregisterRequest()
and all child functions toJSONObject
.suspend fun registerRequest(sessionId: String): ApiResult<JSONObject> { val call = client.newCall( Request.Builder() .url("$BASE_URL/<your api url>") .addHeader("Cookie", formatCookie(sessionId)) .method("POST", jsonRequestBody { name("attestation").value("none") name("authenticatorSelection").objectValue { name("authenticatorAttachment").value("platform") name("userVerification").value("required") name("residentKey").value("required") } }).build() ) val response = call.await() return response.result("Error calling the api") { parsePublicKeyCredentialCreationOptions( body ?: throw ApiException("Empty response from the api call") ) } }
Safely remove any methods that handle intent launcher and activity result calls from your view.
Since
registerRequest()
now returns aJSONObject
, you don't need to create aPendingIntent
. Replace the returned intent with aJSONObject
. Update your intent launcher calls to callcreateCredential()
from the Credential Manager API. CallcreateCredential()
API method.suspend fun createPasskey( activity: Activity, requestResult: JSONObject ): CreatePublicKeyCredentialResponse? { val request = CreatePublicKeyCredentialRequest(requestResult.toString()) var response: CreatePublicKeyCredentialResponse? = null try { response = credMan.createCredential( request as CreateCredentialRequest, activity ) as CreatePublicKeyCredentialResponse } catch (e: CreateCredentialException) { showErrorAlert(activity, e) return null } return response }
Once the call is successful, send the response back to the server. The request and response for this call are similar to the FIDO2 implementation, so no changes are required.
Authenticate with passkeys
After you set up passkey creation, you can set up your app to allow users to sign in and authenticate using their passkeys. To do this, you'll update your authentication code to handle Credential Manager results, and implement a function to authenticate through passkeys.
- Your sign in request call to the server to get necessary information to be
sent to
getCredential()
request is the same as the FIDO2 implementation. No changes are required. Similar to the register request call, the returned response is in JSONObject format.
/** * @param sessionId The session ID to be used for the sign-in. * @param credentialId The credential ID of this device. * @return a JSON object. */ suspend fun signinRequest(): ApiResult<JSONObject> { val call = client.newCall(Builder().url(buildString { append("$BASE_URL/signinRequest") }).method("POST", jsonRequestBody {}) .build() ) val response = call.await() return response.result("Error calling /signinRequest") { parsePublicKeyCredentialRequestOptions( body ?: throw ApiException("Empty response from /signinRequest") ) } } /** * @param sessionId The session ID to be used for the sign-in. * @param response The JSONObject for signInResponse. * @param credentialId id/rawId. * @return A list of all the credentials registered on the server, * including the newly-registered one. */ suspend fun signinResponse( sessionId: String, response: JSONObject, credentialId: String ): ApiResult<Unit> { val call = client.newCall( Builder().url("$BASE_URL/signinResponse") .addHeader("Cookie",formatCookie(sessionId)) .method("POST", jsonRequestBody { name("id").value(credentialId) name("type").value(PUBLIC_KEY.toString()) name("rawId").value(credentialId) name("response").objectValue { name("clientDataJSON").value( response.getString("clientDataJSON") ) name("authenticatorData").value( response.getString("authenticatorData") ) name("signature").value( response.getString("signature") ) name("userHandle").value( response.getString("userHandle") ) } }).build() ) val apiResponse = call.await() return apiResponse.result("Error calling /signingResponse") { } }
Safely remove any methods that handle the intent launcher and activity result calls from your view.
Since
signInRequest()
now returns aJSONObject
, you don't need to create aPendingIntent
. Replace the returned intent with aJSONObject
, and callgetCredential()
from your API methods.suspend fun getPasskey( activity: Activity, creationResult: JSONObject ): GetCredentialResponse? { Toast.makeText( activity, "Fetching previously stored credentials", Toast.LENGTH_SHORT) .show() var result: GetCredentialResponse? = null try { val request= GetCredentialRequest( listOf( GetPublicKeyCredentialOption( creationResult.toString(), null ), GetPasswordOption() ) ) result = credMan.getCredential(activity, request) if (result.credential is PublicKeyCredential) { val publicKeycredential = result.credential as PublicKeyCredential Log.i("TAG", "Passkey ${publicKeycredential.authenticationResponseJson}") return result } } catch (e: Exception) { showErrorAlert(activity, e) } return result }
Once the call is successful, send the response back to the server to validate and authenticate the user. The request and response parameters for this API call are similar to the FIDO2 implementation, so no changes are required.
Additional resources
- Credential Manager Sample reference
- Credential Manager Codelab
- Bringing seamless authentication to your apps with passkeys using Credential Manager API
- FIDO2 codelab