A página Começar a usar o Firebase Phone Number Verification detalha como fazer a integração
com o Firebase PNV usando o método getVerifiedPhoneNumber()
, que processa
todo o fluxo do Firebase PNV, desde a obtenção do consentimento do usuário até a realização das
chamadas de rede necessárias para o back-end do Firebase PNV.
A API de método único (getVerifiedPhoneNumber()
) é recomendada para a maioria dos
desenvolvedores. No entanto, se você precisar de um controle mais refinado sobre a interação com o
Gerenciador de credenciais do Android, por exemplo, para solicitar outras credenciais junto com
o número de telefone, a
biblioteca Firebase PNV também oferece os dois métodos a seguir, cada um deles processando
uma interação diferente com o back-end Firebase PNV:
- O
getDigitalCredentialPayload()
recebe uma solicitação assinada pelo servidor que você vai usar para invocar o Gerenciador de credenciais. - O
exchangeCredentialResponseForPhoneNumber()
troca a resposta do Credential Manager por um token assinado que contém o número de telefone verificado.
Entre a chamada de cada um desses métodos, você é responsável por processar a interação com as APIs do Credential Manager do Android. Esta página oferece uma visão geral de como implementar esse fluxo de três partes.
Antes de começar
Configure o projeto do Firebase e importe as dependências do Firebase PNV conforme descrito na página Começar.
1. Receber o payload da solicitação de credencial digital
Chame o método getDigitalCredentialPayload()
para gerar uma solicitação do
número de telefone do dispositivo. Na próxima etapa, essa solicitação será o payload da
interação com a 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. Fazer uma solicitação de credencial digital usando o Gerenciador de credenciais
Em seguida, transmita a solicitação ao Gerenciador de credenciais.
Para isso, encapsule o payload da solicitação em uma solicitação da API DigitalCredential. Essa solicitação precisa incluir o mesmo nonce transmitido para
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()
Depois disso, faça a solicitação usando a 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 a chamada do Credential Manager for bem-sucedida, a resposta vai conter uma credencial digital, que pode ser extraída usando um código como o exemplo a seguir:
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. Trocar a resposta da credencial digital por um token Firebase PNV
Por fim, chame o método exchangeCredentialResponseForPhoneNumber()
para
trocar a resposta da credencial digital pelo número de telefone verificado e um
token Firebase PNV:
fpnv.exchangeCredentialResponseForPhoneNumber(dcApiResponse)
.addOnSuccessListener { result ->
val phoneNumber = result.getPhoneNumber()
// Verification successful
}
.addOnFailureListener { e -> /* Handle exchange failure */ }
4. Como verificar o token Firebase PNV
Se o fluxo for bem-sucedido, o método getVerifiedPhoneNumber()
vai retornar o número de telefone verificado e um token assinado que o contém. Você pode usar esses dados no seu app, conforme documentado na sua política de privacidade.
Se você usar o número de telefone verificado fora do cliente do app, transmita o token em vez do número de telefone para verificar a integridade dele quando o usar. Para verificar tokens, você precisa implementar dois endpoints:
- Um endpoint de geração de valor de uso único
- Um endpoint de verificação de token
A implementação desses endpoints é de sua responsabilidade. Os exemplos a seguir mostram como implementá-los usando Node.js e Express.
Gerar valores de uso único
Esse endpoint é responsável por gerar e armazenar temporariamente valores de uso único chamados de nonces, que são usados para evitar ataques de repetição contra seus endpoints. Por exemplo, você pode ter uma rota do Express definida assim:
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 });
});
Esse é o endpoint que a função de marcador de posição,
fetchNonceFromYourServer()
, na Etapa 1 chamaria. O nonce vai se propagar
pelas várias chamadas de rede que o cliente realiza e, eventualmente, voltar
ao seu servidor no token Firebase PNV. Na próxima etapa, você vai verificar se o token contém um nonce gerado por você.
Como verificar tokens
Esse endpoint recebe tokens Firebase PNV do seu cliente e verifica a autenticidade deles. Para verificar um token, você precisa conferir:
O token é assinado usando uma das chaves publicadas no endpoint Firebase PNV JWKS:
https://fpnv.googleapis.com/v1beta/jwks
As declarações de público-alvo e emissor contêm o número do seu projeto do Firebase e estão no seguinte formato:
https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBER
Você pode encontrar o número do projeto do Firebase na página Configurações do projeto do console do Firebase.
O token não expirou.
O token contém um número único válido. Um nonce é válido se:
- Você o gerou (ou seja, ele pode ser encontrado em qualquer mecanismo de persistência que você esteja usando).
- Ele ainda não foi usado
- Não expirou
Por exemplo, a implementação do Express pode ser parecida com esta:
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);
}
});