Auf der Seite Erste Schritte mit Firebase Phone Number Verification wird beschrieben, wie Sie Firebase PNV mit der Methode getVerifiedPhoneNumber()
einbinden. Diese Methode übernimmt den gesamten Firebase PNV-Ablauf, von der Einholung der Nutzereinwilligung bis hin zu den erforderlichen Netzwerkaufrufen des Firebase PNV-Back-ends.
Die API mit einer Methode (getVerifiedPhoneNumber()
) wird für die meisten Entwickler empfohlen. Wenn Sie jedoch eine detailliertere Kontrolle über die Interaktion mit dem Android Credential Manager benötigen, z. B. um neben der Telefonnummer auch andere Anmeldedaten anzufordern, bietet die Firebase PNV-Bibliothek auch die folgenden beiden Methoden, die jeweils eine andere Interaktion mit dem Firebase PNV-Backend verarbeiten:
getDigitalCredentialPayload()
erhält eine serverseitig signierte Anfrage, mit der Sie den Credential Manager aufrufen.exchangeCredentialResponseForPhoneNumber()
tauscht die Antwort von Credential Manager gegen ein signiertes Token mit der bestätigten Telefonnummer ein.
Zwischen den Aufrufen der einzelnen Methoden müssen Sie die Interaktion mit den Credential Manager APIs von Android verarbeiten. Auf dieser Seite erhalten Sie einen Überblick über die Implementierung dieses dreiteiligen Ablaufs.
Hinweis
Richten Sie Ihr Firebase-Projekt ein und importieren Sie die Firebase PNV-Abhängigkeiten, wie auf der Seite Erste Schritte beschrieben.
1. Nutzlast der Anfrage für digitale Anmeldedaten abrufen
Rufen Sie die Methode getDigitalCredentialPayload()
auf, um eine Anfrage für die Telefonnummer des Geräts zu generieren. Im nächsten Schritt ist diese Anfrage die Nutzlast Ihrer Interaktion mit der 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. Digitale Anmeldedaten mit dem Credential Manager anfordern
Übergeben Sie die Anfrage als Nächstes an den Credential Manager.
Dazu müssen Sie die Anfrage-Nutzlast in eine DigitalCredential API-Anfrage einbetten. Diese Anfrage muss dieselbe Nonce enthalten, die Sie an getDigitalCredentialPayload()
übergeben haben.
// 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()
Anschließend können Sie die Anfrage mit der Credential Manager API stellen:
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
}
Wenn der Credential Manager-Aufruf erfolgreich ist, enthält die Antwort ein digitales Anmeldedatenobjekt, das Sie mit Code wie im folgenden Beispiel extrahieren können:
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. Antwort auf digitale Anmeldedaten gegen ein Firebase PNV-Token eintauschen
Rufen Sie schließlich die Methode exchangeCredentialResponseForPhoneNumber()
auf, um die Antwort auf die digitale Anmeldedatenanfrage gegen die bestätigte Telefonnummer und ein Firebase PNV-Token einzutauschen:
fpnv.exchangeCredentialResponseForPhoneNumber(dcApiResponse)
.addOnSuccessListener { result ->
val phoneNumber = result.getPhoneNumber()
// Verification successful
}
.addOnFailureListener { e -> /* Handle exchange failure */ }
4. Firebase PNV-Token überprüfen
Wenn der Ablauf erfolgreich ist, gibt die Methode getVerifiedPhoneNumber()
die bestätigte Telefonnummer und ein signiertes Token zurück, das sie enthält. Sie können diese Daten in Ihrer App gemäß Ihrer Datenschutzerklärung verwenden.
Wenn Sie die bestätigte Telefonnummer außerhalb des App-Clients verwenden, sollten Sie das Token anstelle der Telefonnummer selbst übergeben, damit Sie die Integrität des Tokens bei der Verwendung überprüfen können. Zum Bestätigen von Tokens müssen Sie zwei Endpunkte implementieren:
- Ein Endpunkt für die Nonce-Generierung
- Ein Endpunkt für die Tokenüberprüfung
Die Implementierung dieser Endpunkte liegt bei Ihnen. Die folgenden Beispiele zeigen, wie Sie sie mit Node.js und Express implementieren können.
Nonces generieren
Dieser Endpunkt ist für das Generieren und vorübergehende Speichern von Einmalwerten (sogenannten Nonces) verantwortlich, die verwendet werden, um Replay-Angriffe auf Ihre Endpunkte zu verhindern. Beispiel: Sie haben eine Express-Route, die so definiert ist:
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 });
});
Dies ist der Endpunkt, der von der Platzhalterfunktion fetchNonceFromYourServer()
in Schritt 1 aufgerufen wird. Die Nonce wird über die verschiedenen Netzwerkanrufe, die der Client ausführt, weitergegeben und gelangt schließlich im Firebase PNV-Token zurück zu Ihrem Server. Im nächsten Schritt prüfen Sie, ob das Token eine von Ihnen generierte Nonce enthält.
Tokens überprüfen
Dieser Endpunkt empfängt Firebase PNV-Tokens von Ihrem Client und überprüft ihre Authentizität. So überprüfen Sie ein Token:
Das Token wird mit einem der Schlüssel signiert, die am JWKS-Endpunkt Firebase PNV veröffentlicht wurden:
https://fpnv.googleapis.com/v1beta/jwks
Die Ansprüche „audience“ und „issuer“ enthalten Ihre Firebase-Projektnummer und haben das folgende Format:
https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBER
Sie finden Ihre Firebase-Projektnummer in der Firebase Console auf der Seite Projekteinstellungen.
Das Token ist nicht abgelaufen.
Das Token enthält einen gültigen Nonce-Wert. Eine Nonce ist gültig, wenn:
- Sie haben es generiert (d. h., es ist in dem von Ihnen verwendeten Persistenzmechanismus zu finden).
- Er darf nicht bereits verwendet worden sein.
- Sie ist noch gültig.
Die Express-Implementierung könnte beispielsweise so aussehen:
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);
}
});