Register a Passkey with the Service
and attest the knowledge of a KeyPair.
This is done by creating a new Passkey with the liquid extension enabled.
The extension is used to sign the challenge with an additional KeyPair as well as authenticate a remote peer.
Get started by initializing the AttestationApi with an OkHttpClient and CookieJar.
val httpClient = OkHttpClient. Builder ()
. cookieJar (cookieJar) // Use Cookie jar to share cookies between requests
val attestationApi = AttestationApi (httpClient)
👤 User Agent
The User-Agent string is used to authenticate the device.
// UA used to authenticate the device
val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} " +
"(Android ${Build.VERSION.RELEASE}; ${Build.MODEL}; ${Build.BRAND})"
🧮 Options
Fetch attestation options from the service.
The remote client should present a Deep Link which contains the Origin.
// Create Options for FIDO2 Server
val options = JSONObject ()
options. put ( "username" , account.address. toString ())
options. put ( "displayName" , "Liquid Auth User" )
options. put ( "authenticatorSelection" , JSONObject (). put ( "userVerification" , "required" ))
// Enable Liquid Extension
val extensions = JSONObject ()
extensions. put ( "liquid" , true )
options. put ( "extensions" , extensions)
val response = attestationApi. postAttestationOptions (
userAgent, // Required for checking the authenticator fingerprint
options // Additional Request Options
✨ Creating
There are several ways to handle the creation of a new Passkey.
We recommend
using the FIDO2ApiClient or CredentialManager
val fido2Client = Fido2ApiClient (context)
val origin = "https://my-liquid-service.com"
// Fetch Attestation Options
val response = attestationApi. postAttestationOptions (origin, userAgent, options). await ()
// Parse the response to PublicKeyCredentialCreationOptions
val pubKeyCredentialCreationOptions = response.body !! . toPublicKeyCredentialCreationOptions ()
// Sign the challenge with the additional keypair you have custody of
val signature = KeyPairs. rawSignBytes (
pubKeyCredentialCreationOptions.challenge,
// Activity Result Handler
fun handleAuthenticatorAttestationResult (activityResult: ActivityResult){
val bytes = activityResult. data ?. getByteArrayExtra (Fido.FIDO2_KEY_CREDENTIAL_EXTRA)
activityResult.resultCode != Activity.RESULT_OK ->
* Handle the PublicKeyCredential
* Now you can combine the PublicKeyCredential with the Liquid Extension JSON
* and submit it to the service for verification
val credential = PublicKeyCredential. deserializeFromBytes (bytes)
// Register/Attestation Intent Launcher
val attestationIntentLauncher = registerForActivityResult (
ActivityResultContracts. StartIntentSenderForResult (),
:: handleAuthenticatorAttestationResult
// Launch the FIDO2 Intent
val pendingIntent = fido2Client. getRegisterPendingIntent (pubKeyCredentialCreationOptions). await ()
attestationIntentLauncher. launch (
IntentSenderRequest. Builder (pendingIntent). build ()
import foundation.algorand.auth.fido2. *
val credential = PublicKeyCredential ()
🔐 Liquid Extension
Sign the challenge with an additional KeyPair and optionally authenticate a remote peer.
// Create the Liquid Extension JSON
val liquidExtJSON = JSONObject ()
// The type of signature and public key, this is also used
// to determine the type of encoding for the user.id
liquidExtJSON. put ( "type" , "algorand" )
// The address of the account
liquidExtJSON. put ( "address" , account.address. toString ())
// The signature of the challenge, signed by the account
liquidExtJSON. put ( "signature" , Base64. encodeBase64URLSafeString (signature))
// Optionally authenticate a remote peer
liquidExtJSON. put ( "requestId" , "<UUID_FROM_QR_CODE>" )
liquidExtJSON. put ( "device" , Build.MODEL)
🚚 Response
val response = attestationApi. postAttestationResponse (
"User-Agent-String" , // Required for checking the authenticator fingerprint
credential, // PublicKeyCredential
liquidExtJSON // Liquid Extension JSON