Halaman Mulai menggunakan Firebase Phone Number Verification menjelaskan cara berintegrasi dengan Firebase PNV menggunakan metode getVerifiedPhoneNumber()
, yang menangani seluruh alur Firebase PNV, mulai dari mendapatkan izin pengguna hingga melakukan panggilan jaringan yang diperlukan ke backend Firebase PNV.
API satu metode (getVerifiedPhoneNumber()
) direkomendasikan untuk sebagian besar
developer. Namun, jika Anda memerlukan kontrol yang lebih terperinci atas interaksi dengan
Android Credential Manager–misalnya, untuk meminta kredensial lain bersama dengan
nomor telepon–library
Firebase PNV juga menyediakan dua metode berikut, yang masing-masing menangani
interaksi yang berbeda dengan backend Firebase PNV:
getDigitalCredentialPayload()
mendapatkan permintaan bertanda tangan server yang akan Anda gunakan untuk memanggil Credential Manager.exchangeCredentialResponseForPhoneNumber()
menukarkan respons dari Credential Manager dengan token bertanda tangan yang berisi nomor telepon terverifikasi.
Di antara panggilan setiap metode tersebut, Anda bertanggung jawab untuk menangani interaksi dengan API Pengelola Kredensial Android. Halaman ini memberikan ringkasan tentang cara Anda menerapkan alur tiga bagian ini.
Sebelum memulai
Siapkan project Firebase Anda dan impor dependensi Firebase PNV seperti yang dijelaskan di halaman Mulai.
1. Mendapatkan payload permintaan Kredensial Digital
Panggil metode getDigitalCredentialPayload()
untuk membuat permintaan nomor telepon perangkat. Pada langkah berikutnya, permintaan ini akan menjadi payload
interaksi Anda dengan Credential Manager API.
// This instance does not require an Activity context.
val fpnv = FirebasePhoneNumberVerification.getInstance()
// Your request should include a nonce, which will propagate through the flow
// and be present in the final response from FPNV. See the section "Verifying
// the Firebase PNV token" for details on generating and verifying this.
val nonce = fetchNonceFromYourServer()
fpnv.getDigitalCredentialPayload(nonce, "https://example.com/privacy-policy")
.addOnSuccessListener { fpnvDigitalCredentialPayload ->
// Use the payload in the next step.
// ...
}
.addOnFailureListener { e -> /* Handle payload fetch failure */ }
2. Membuat permintaan kredensial digital menggunakan Pengelola Kredensial
Selanjutnya, teruskan permintaan ke Credential Manager.
Untuk melakukannya, Anda perlu membungkus payload permintaan dalam permintaan DigitalCredential API. Permintaan ini harus menyertakan nonce yang sama dengan yang Anda teruskan ke
getDigitalCredentialPayload()
.
// This example uses string interpolation for clarity, but you should use some kind of type-safe
// serialization method.
fun buildDigitalCredentialRequestJson(nonce: String, fpnvDigitalCredentialPayload: String) = """
{
"requests": [
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"response_type": "vp_token",
"response_mode": "dc_api",
"nonce": "$nonce",
"dcql_query": { "credentials": [$fpnvDigitalCredentialPayload] }
}
}
]
}
""".trimIndent()
Setelah melakukannya, Anda dapat membuat permintaan menggunakan Credential Manager API:
suspend fun makeFpnvRequest(
context: Activity, nonce: String, fpnvDigitalCredentialPayload: String): GetCredentialResponse {
// Helper function to build the digital credential request (defined above).
// Pass the same nonce you passed to getDigitalCredentialPayload().
val digitalCredentialRequestJson =
buildDigitalCredentialRequestJson(nonce, fpnvDigitalCredentialPayload)
// CredentialManager requires an Activity context.
val credentialManager = CredentialManager.create(context)
// Build a Credential Manager request that includes the Firebase PNV option. Note that
// you can't combine the digital credential option with other options.
val request = GetCredentialRequest.Builder()
.addCredentialOption(GetDigitalCredentialOption(digitalCredentialRequestJson))
.build()
// getCredential is a suspend function, so it must run in a coroutine scope,
val cmResponse: GetCredentialResponse = try {
credentialManager.getCredential(context, request)
} catch (e: GetCredentialException) {
// If the user cancels the operation, the feature isn't available, or the
// SIM doesn't support the feature, a GetCredentialCancellationException
// will be returned. Otherwise, a GetCredentialUnsupportedException will
// be returned with details in the exception message.
throw e
}
return cmResponse
}
Jika panggilan Credential Manager berhasil, responsnya akan berisi kredensial digital, yang dapat Anda ekstrak menggunakan kode seperti contoh berikut:
val dcApiResponse = extractApiResponse(cmResponse)
fun extractApiResponse(response: GetCredentialResponse): String {
val credential = response.credential
when (credential) {
is DigitalCredential -> {
val json = JSONObject(credential.credentialJson)
val firebaseJwtArray =
json.getJSONObject("data").getJSONObject("vp_token").getJSONArray("firebase")
return firebaseJwtArray.getString(0)
}
else -> {
// Handle any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential ${credential.type}")
}
}
}
3. Menukarkan respons kredensial digital dengan token Firebase PNV
Terakhir, panggil metode exchangeCredentialResponseForPhoneNumber()
untuk
menukar respons kredensial digital dengan nomor telepon terverifikasi dan token
Firebase PNV:
fpnv.exchangeCredentialResponseForPhoneNumber(dcApiResponse)
.addOnSuccessListener { result ->
val phoneNumber = result.getPhoneNumber()
// Verification successful
}
.addOnFailureListener { e -> /* Handle exchange failure */ }
4. Memverifikasi token Firebase PNV
Jika alur berhasil, metode getVerifiedPhoneNumber()
akan menampilkan nomor telepon terverifikasi dan token bertanda tangan yang berisi nomor tersebut. Anda dapat menggunakan data ini di aplikasi Anda
seperti yang didokumentasikan oleh kebijakan privasi Anda.
Jika Anda menggunakan nomor telepon terverifikasi di luar klien aplikasi, Anda harus meneruskan token, bukan nomor telepon itu sendiri, sehingga Anda dapat memverifikasi integritasnya saat Anda menggunakannya. Untuk memverifikasi token, Anda perlu menerapkan dua endpoint:
- Endpoint pembuatan nonce
- Endpoint verifikasi token
Implementasi endpoint ini terserah Anda; contoh berikut menunjukkan cara mengimplementasikannya menggunakan Node.js dan Express.
Membuat nonce
Endpoint ini bertanggung jawab untuk membuat dan menyimpan sementara nilai sekali pakai yang disebut nonce, yang digunakan untuk mencegah serangan replay terhadap endpoint Anda. Sebagai contoh, Anda mungkin memiliki rute Express yang ditentukan seperti ini:
app.get('/fpnvNonce', async (req, res) => {
const nonce = crypto.randomUUID();
// TODO: Save the nonce to a database, key store, etc.
// You should also assign the nonce an expiration time and periodically
// clear expired nonces from your database.
await persistNonce({
nonce,
expiresAt: Date.now() + 180000, // Give it a short duration.
});
// Return the nonce to the caller.
res.send({ nonce });
});
Ini adalah endpoint yang akan dipanggil oleh fungsi placeholder,
fetchNonceFromYourServer()
, di Langkah 1. Nonce akan disebarkan melalui berbagai panggilan jaringan yang dilakukan klien dan akhirnya dikirim kembali ke server Anda dalam token Firebase PNV. Pada langkah berikutnya, Anda akan memverifikasi
bahwa token berisi nonce yang Anda buat.
Memverifikasi token
Endpoint ini menerima token Firebase PNV dari klien Anda dan memverifikasi keasliannya. Untuk memverifikasi token, Anda perlu memeriksa:
Token ditandatangani menggunakan salah satu kunci yang dipublikasikan di endpoint JWKS Firebase PNV:
https://fpnv.googleapis.com/v1beta/jwks
Klaim audiens dan penerbit berisi nomor project Firebase Anda dan dalam format berikut:
https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBER
Anda dapat menemukan nomor project Firebase di halaman Project settings di Firebase console.
Masa berlaku token belum berakhir.
Token berisi nonce yang valid. Nonce valid jika:
- Anda membuatnya (yaitu, dapat ditemukan di mekanisme persistensi apa pun yang Anda gunakan)
- Nama tersebut belum digunakan
- Masa berlakunya belum habis
Misalnya, penerapan Express mungkin terlihat seperti berikut:
import { JwtVerifier } from "aws-jwt-verify";
// Find your Firebase project number in the Firebase console.
const FIREBASE_PROJECT_NUMBER = "123456789";
// The issuer and audience claims of the FPNV token are specific to your
// project.
const issuer = `https://fpnv.googleapis.com/projects/${FIREBASE_PROJECT_NUMBER}`;
const audience = `https://fpnv.googleapis.com/projects/${FIREBASE_PROJECT_NUMBER}`;
// The JWKS URL contains the current public signing keys for FPNV tokens.
const jwksUri = "https://fpnv.googleapis.com/v1beta/jwks";
// Configure a JWT verifier to check the following:
// - The token is signed by Google
// - The issuer and audience claims match your project
// - The token has not yet expired (default begavior)
const fpnvVerifier = JwtVerifier.create({ issuer, audience, jwksUri });
app.post('/verifiedPhoneNumber', async (req, res) => {
if (!req.body) return res.sendStatus(400);
// Get the token from the body of the request.
const fpnvToken = req.body;
try {
// Attempt to verify the token using the verifier configured above.
const verifiedPayload = await fpnvVerifier.verify(fpnvToken);
// Now that you've verified the signature and claims, verify the nonce.
// TODO: Try to look up the nonce in your database and remove it if it's
// found; if it's not found or it's expired, throw an error.
await testAndRemoveNonce(verifiedPayload.nonce);
// Only after verifying the JWT signature, claims, and nonce, get the
// verified phone number from the subject claim.
// You can use this value however it's needed by your app.
const verifiedPhoneNumber = verifiedPayload.sub;
// (Do something with it...)
return res.sendStatus(200);
} catch {
// If verification fails, reject the token.
return res.sendStatus(400);
}
});