Na stronie Pierwsze kroki z Firebase Phone Number Verification znajdziesz szczegółowe informacje o tym, jak przeprowadzić integrację z Firebase PNV za pomocą metody getVerifiedPhoneNumber(), która obsługuje cały proces Firebase PNV – od uzyskania zgody użytkownika po wykonywanie niezbędnych wywołań sieciowych do backendu Firebase PNV.
Większości deweloperów zalecamy korzystanie z interfejsu API z jedną metodą (getVerifiedPhoneNumber()). Jeśli jednak potrzebujesz bardziej szczegółowej kontroli nad interakcją z Menedżerem danych logowania Androida, np. aby poprosić o inne dane logowania wraz z numerem telefonu, biblioteka Firebase PNV udostępnia też te 2 metody, z których każda obsługuje inną interakcję z backendem Firebase PNV:
getDigitalCredentialPayload()otrzymuje podpisane przez serwer żądanie, którego użyjesz do wywołania Credential Managera.exchangeCredentialResponseForPhoneNumber()wymienia odpowiedź z Credential Manager na podpisany token zawierający zweryfikowany numer telefonu. Na tym etapie nastąpi naliczenie opłat.
Pomiędzy wywołaniem każdej z tych metod odpowiadasz za obsługę interakcji z interfejsami API Credential Manager Androida. Na tej stronie znajdziesz omówienie sposobu wdrażania tego 3-częściowego procesu.
Zanim zaczniesz
Skonfiguruj projekt w Firebase i zaimportuj zależności Firebase PNV zgodnie z opisem na stronie Pierwsze kroki.
1. Pobieranie ładunku żądania cyfrowego dokumentu tożsamości
Wywołaj metodę getDigitalCredentialPayload(), aby wygenerować prośbę o numer telefonu urządzenia. W następnym kroku to żądanie będzie ładunkiem interakcji z interfejsem Credential Manager API.
Kotlin
// 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. Wysyłanie żądania cyfrowego dokumentu tożsamości za pomocą Menedżera danych logowania
Następnie przekaż żądanie do Credential Manager.
Aby to zrobić, musisz opakować ładunek żądania w żądanie interfejsu DigitalCredential API. To żądanie musi zawierać tę samą liczbę jednorazową, która została przekazana do getDigitalCredentialPayload().
Kotlin
// 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()
Po wykonaniu tej czynności możesz wysłać żądanie za pomocą interfejsu Credential Manager API:
Kotlin
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
}
Jeśli wywołanie Menedżera danych logowania się powiedzie, odpowiedź będzie zawierać cyfrowy dokument tożsamości, który możesz wyodrębnić za pomocą kodu, jak w tym przykładzie:
Kotlin
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. Wymień odpowiedź dotyczącą cyfrowego dokumentu tożsamości na token Firebase PNV
Na koniec wywołaj metodę exchangeCredentialResponseForPhoneNumber(), aby wymienić odpowiedź cyfrowego dokumentu tożsamości na zweryfikowany numer telefonu i token Firebase PNV:
Kotlin
fpnv.exchangeCredentialResponseForPhoneNumber(dcApiResponse)
.addOnSuccessListener { result ->
val phoneNumber = result.getPhoneNumber()
// Verification successful
}
.addOnFailureListener { e -> /* Handle exchange failure */ }
Po pomyślnym zakończeniu tego kroku i zwróceniu zweryfikowanego numeru telefonu do aplikacji zostanie uruchomione rozliczanie.
4. Weryfikowanie tokena Firebase PNV
Jeśli proces się powiedzie, metoda getVerifiedPhoneNumber() zwróci zweryfikowany numer telefonu i podpisany token, który go zawiera. Możesz używać tych danych w aplikacji
zgodnie z polityką prywatności.
Jeśli używasz zweryfikowanego numeru telefonu poza klientem aplikacji, zamiast samego numeru telefonu przekazuj token, aby móc zweryfikować jego integralność podczas używania. Aby zweryfikować tokeny, musisz zaimplementować 2 punkty końcowe:
- punkt końcowy generowania liczby jednorazowej,
- punkt końcowy weryfikacji tokena,
Implementacja tych punktów końcowych zależy od Ciebie. Poniższe przykłady pokazują, jak możesz je zaimplementować za pomocą Node.js i Express.
Generowanie liczb jednorazowych
Ten punkt końcowy odpowiada za generowanie i tymczasowe przechowywanie jednorazowych wartości nazywanych liczbami jednorazowymi, które służą do zapobiegania atakom metodą powtórzenia na punkty końcowe. Możesz na przykład zdefiniować trasę Express w ten sposób:
Node.js
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 });
});
Jest to punkt końcowy, który wywołuje funkcja zastępcza fetchNonceFromYourServer() w kroku 1. Liczba jednorazowa będzie przekazywana przez różne wywołania sieciowe wykonywane przez klienta i ostatecznie wróci na serwer w tokenie Firebase PNV. W następnym kroku sprawdzisz, czy token zawiera wygenerowaną przez Ciebie liczbę jednorazową.
Weryfikowanie tokenów
Ten punkt końcowy otrzymuje tokeny Firebase PNV od klienta i weryfikuje ich autentyczność. Aby zweryfikować token, musisz sprawdzić:
Nagłówek
typjest ustawiony naJWT.Token jest podpisany za pomocą jednego z kluczy opublikowanych w punkcie końcowym JWKS Firebase PNV z algorytmem
ES256:https://fpnv.googleapis.com/v1beta/jwksOświadczenia wystawcy zawierają numer projektu w Firebase i mają ten format:
https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBERNumer projektu w Firebase znajdziesz na karcie
Ustawienia > Ogólne w konsoli Firebase.Deklaracja odbiorców to lista zawierająca numer i identyfikator projektu w Firebase w tym formacie:
[ https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBER, https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_ID, ]Token nie stracił ważności.
Token zawiera prawidłową liczbę jednorazową. Liczba jednorazowa jest prawidłowa, jeśli:
- wygenerowałeś(-aś) go (czyli można go znaleźć w dowolnym mechanizmie trwałości, którego używasz);
- nie został jeszcze wykorzystany,
- nie wygasł,
Implementacja Express może wyglądać na przykład tak:
Node.js
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);
}
});