La pagina Inizia a utilizzare Firebase Phone Number Verification descrive in dettaglio come eseguire l'integrazione
con Firebase PNV utilizzando il metodo getVerifiedPhoneNumber()
, che gestisce
l'intero flusso Firebase PNV, dall'ottenimento del consenso dell'utente all'esecuzione delle
chiamate di rete necessarie al backend Firebase PNV.
L'API a metodo singolo (getVerifiedPhoneNumber()
) è consigliata per la maggior parte
degli sviluppatori. Tuttavia, se hai bisogno di un controllo più granulare sull'interazione con
Android Credential Manager, ad esempio per richiedere altre credenziali insieme
al numero di telefono, la
libreria Firebase PNV fornisce anche i due metodi seguenti, ognuno dei quali gestisce
un'interazione diversa con il backend Firebase PNV:
getDigitalCredentialPayload()
riceve una richiesta firmata dal server che utilizzerai per richiamare Credential Manager.exchangeCredentialResponseForPhoneNumber()
scambia la risposta di Credential Manager con un token firmato contenente il numero di telefono verificato.
Tra una chiamata e l'altra di questi metodi, sei responsabile della gestione dell'interazione con le API Credential Manager di Android. Questa pagina fornisce una panoramica su come implementare questo flusso in tre parti.
Prima di iniziare
Configura il tuo progetto Firebase e importa le dipendenze Firebase PNV come descritto nella pagina Inizia.
1. Recuperare il payload della richiesta di credenziali digitali
Chiama il metodo getDigitalCredentialPayload()
per generare una richiesta per il numero di telefono del dispositivo. Nel passaggio successivo, questa richiesta sarà il payload della tua interazione con l'API Credential Manager.
// 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. Effettuare una richiesta di credenziali digitali utilizzando Credential Manager
Quindi, passa la richiesta a Credential Manager.
Per farlo, devi racchiudere il payload della richiesta in una richiesta dell'API DigitalCredential. Questa richiesta deve includere lo stesso nonce che hai passato a
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()
Una volta fatto, puoi effettuare la richiesta utilizzando l'API Credential Manager:
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
}
Se la chiamata a Credential Manager ha esito positivo, la risposta conterrà una credenziale digitale, che puoi estrarre utilizzando un codice come il seguente esempio:
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. Scambia la risposta delle credenziali digitali con un token Firebase PNV
Infine, chiama il metodo exchangeCredentialResponseForPhoneNumber()
per
scambiare la risposta della credenziale digitale con il numero di telefono verificato e un
token Firebase PNV:
fpnv.exchangeCredentialResponseForPhoneNumber(dcApiResponse)
.addOnSuccessListener { result ->
val phoneNumber = result.getPhoneNumber()
// Verification successful
}
.addOnFailureListener { e -> /* Handle exchange failure */ }
4. Verifica del token Firebase PNV
Se il flusso ha esito positivo, il metodo getVerifiedPhoneNumber()
restituisce il numero di telefono verificato e un token firmato che lo contiene. Puoi utilizzare questi dati nella tua app
come documentato dalle tue norme sulla privacy.
Se utilizzi il numero di telefono verificato al di fuori del client dell'app, devi passare il token anziché il numero di telefono stesso, in modo da poterne verificare l'integrità quando lo utilizzi. Per verificare i token, devi implementare due endpoint:
- Un endpoint di generazione nonce
- Un endpoint di verifica dei token
L'implementazione di questi endpoint dipende da te; i seguenti esempi mostrano come potresti implementarli utilizzando Node.js ed Express.
Generazione di nonce
Questo endpoint è responsabile della generazione e dell'archiviazione temporanea di valori monouso chiamati nonce, che vengono utilizzati per impedire attacchi di riproduzione contro i tuoi endpoint. Ad esempio, potresti avere una route Express definita in questo modo:
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 });
});
Questo è l'endpoint che chiamerebbe la funzione segnaposto,
fetchNonceFromYourServer()
, nel passaggio 1. Il nonce si propagherà
attraverso le varie chiamate di rete eseguite dal client e alla fine tornerà
al tuo server nel token Firebase PNV. Nel passaggio successivo, verifichi
che il token contenga un nonce che hai generato.
Verifica dei token
Questo endpoint riceve i token Firebase PNV dal tuo client e ne verifica l'autenticità. Per verificare un token, devi controllare:
Il token è firmato utilizzando una delle chiavi pubblicate nell'endpoint JWKS Firebase PNV:
https://fpnv.googleapis.com/v1beta/jwks
Le rivendicazioni audience e emittente contengono il numero del tuo progetto Firebase e hanno il seguente formato:
https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBER
Puoi trovare il numero del progetto Firebase nella pagina Impostazioni progetto della console Firebase.
Il token non è scaduto.
Il token contiene un nonce valido. Un nonce è valido se:
- L'hai generato (ovvero, si trova nel meccanismo di persistenza che utilizzi)
- Non sia già stato utilizzato
- Non sia scaduto
Ad esempio, l'implementazione Express potrebbe avere il seguente aspetto:
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);
}
});