หน้าเริ่มต้นใช้งานFirebase Phone Number Verificationจะให้รายละเอียดเกี่ยวกับวิธีผสานรวม
กับ Firebase PNV โดยใช้เมธอด getVerifiedPhoneNumber() ซึ่งจัดการ
โฟลว์ Firebase PNV ทั้งหมด ตั้งแต่การขอความยินยอมจากผู้ใช้ไปจนถึงการโทรเครือข่ายที่จำเป็นไปยังแบ็กเอนด์ Firebase PNV
เราขอแนะนำให้ใช้ API แบบเมธอดเดียว (getVerifiedPhoneNumber()) สำหรับนักพัฒนาแอปส่วนใหญ่ อย่างไรก็ตาม หากคุณต้องการควบคุมการโต้ตอบกับ Credential Manager ของ Android อย่างละเอียดมากขึ้น เช่น เพื่อขอข้อมูลเข้าสู่ระบบอื่นๆ พร้อมกับหมายเลขโทรศัพท์ ไลบรารี Firebase PNV ยังมี 2 เมธอดต่อไปนี้ ซึ่งแต่ละเมธอดจะจัดการการโต้ตอบที่แตกต่างกันกับแบ็กเอนด์ Firebase PNV
getDigitalCredentialPayload()จะรับคำขอที่เซ็นชื่อโดยเซิร์ฟเวอร์ซึ่งคุณจะใช้เรียกใช้ Credential ManagerexchangeCredentialResponseForPhoneNumber()จะแลกเปลี่ยนการตอบกลับจาก Credential Manager เป็นโทเค็นที่ลงชื่อแล้วซึ่งมีหมายเลขโทรศัพท์ที่ยืนยันแล้ว ระบบจะเรียกเก็บเงินในขั้นตอนนี้
คุณมีหน้าที่รับผิดชอบในการจัดการการโต้ตอบกับ API ของตัวจัดการข้อมูลเข้าสู่ระบบของ Android ระหว่างการเรียกเมธอดแต่ละเมธอด หน้านี้จะแสดงภาพรวมของวิธีที่คุณใช้งานโฟลว์ 3 ส่วนนี้
ก่อนเริ่มต้น
ตั้งค่าโปรเจ็กต์ Firebase และนำเข้าการขึ้นต่อกันของ Firebase PNV ตามที่อธิบายไว้ ในหน้าเริ่มต้นใช้งาน
1. รับเพย์โหลดคำขอข้อมูลเข้าสู่ระบบดิจิทัล
เรียกเมธอด getDigitalCredentialPayload() เพื่อสร้างคำขอหมายเลขโทรศัพท์ของอุปกรณ์ ในขั้นตอนถัดไป คำขอนี้จะเป็นเพย์โหลดของการโต้ตอบกับ 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. ส่งคำขอข้อมูลเข้าสู่ระบบดิจิทัลโดยใช้ Credential Manager
จากนั้นส่งคำขอไปยัง Credential Manager
ในการดำเนินการดังกล่าว คุณต้องห่อเพย์โหลดคำขอในคำขอ API ของข้อมูลเข้าสู่ระบบดิจิทัล คำขอนี้ต้องมี Nonce เดียวกันกับที่คุณส่งไปยัง 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()
เมื่อทำเช่นนั้นแล้ว คุณจะส่งคำขอโดยใช้ 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
}
หากการเรียก Credential Manager สำเร็จ การตอบกลับจะมีข้อมูลเข้าสู่ระบบดิจิทัล ซึ่งคุณสามารถแยกออกมาได้โดยใช้โค้ด เช่น ตัวอย่างต่อไปนี้
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. แลกเปลี่ยนการตอบกลับข้อมูลเข้าสู่ระบบดิจิทัลเป็นโทเค็น Firebase PNV
สุดท้าย ให้เรียกเมธอด exchangeCredentialResponseForPhoneNumber() เพื่อแลกเปลี่ยนการตอบกลับข้อมูลเข้าสู่ระบบดิจิทัลเป็นหมายเลขโทรศัพท์ที่ยืนยันแล้วและโทเค็น Firebase PNV
fpnv.exchangeCredentialResponseForPhoneNumber(dcApiResponse)
.addOnSuccessListener { result ->
val phoneNumber = result.getPhoneNumber()
// Verification successful
}
.addOnFailureListener { e -> /* Handle exchange failure */ }
ขั้นตอนนี้จะทริกเกอร์การเรียกเก็บเงินเมื่อดำเนินการเสร็จสมบูรณ์และระบบส่งหมายเลขโทรศัพท์ที่ยืนยันแล้วกลับไปยังแอป
4. การยืนยันโทเค็น Firebase PNV
หากโฟลว์สำเร็จ เมธอด getVerifiedPhoneNumber() จะแสดงหมายเลขโทรศัพท์ที่ยืนยันแล้วและโทเค็นที่ลงชื่อแล้วซึ่งมีหมายเลขดังกล่าว คุณสามารถใช้ข้อมูลนี้ในแอปตามที่ระบุไว้ในนโยบายความเป็นส่วนตัว
หากคุณใช้หมายเลขโทรศัพท์ที่ยืนยันแล้วนอกไคลเอ็นต์แอป คุณควรส่งโทเค็นแทนหมายเลขโทรศัพท์เองเพื่อให้ยืนยันความสมบูรณ์ของโทเค็นได้เมื่อใช้งาน หากต้องการยืนยันโทเค็น คุณต้องใช้งานปลายทาง 2 รายการ
- ปลายทางสำหรับการสร้าง Nonce
- ปลายทางสำหรับการยืนยันโทเค็น
คุณเป็นผู้กำหนดการใช้งานปลายทางเหล่านี้ ตัวอย่างต่อไปนี้แสดงวิธีที่คุณอาจใช้งานปลายทางเหล่านี้โดยใช้ Node.js และ Express
การสร้าง Nonce
ปลายทางนี้มีหน้าที่สร้างและจัดเก็บค่าที่ใช้ครั้งเดียวชั่วคราวที่เรียกว่า Nonce ซึ่งใช้เพื่อป้องกันการโจมตีแบบ Replay ต่อปลายทางของคุณ ตัวอย่างเช่น คุณอาจกำหนดเส้นทาง Express ไว้ดังนี้
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 });
});
นี่คือปลายทางที่ฟังก์ชันตัวยึดตำแหน่ง fetchNonceFromYourServer() ในขั้นตอนที่ 1 จะเรียก Nonce จะแพร่กระจาย
ผ่านการเรียกเครือข่ายต่างๆ ที่ไคลเอ็นต์ดำเนินการ และในที่สุดก็จะกลับไปยังเซิร์ฟเวอร์ของคุณในโทเค็น Firebase PNV ในขั้นตอนถัดไป คุณจะยืนยันว่าโทเค็นมี Nonce ที่คุณสร้างขึ้น
การยืนยันโทเค็น
ปลายทางนี้จะรับโทเค็น Firebase PNV จากไคลเอ็นต์และยืนยันความถูกต้องของโทเค็น หากต้องการยืนยันโทเค็น คุณต้องตรวจสอบสิ่งต่อไปนี้
ส่วนหัว
typตั้งค่าเป็นJWTโทเค็นลงชื่อโดยใช้คีย์รายการใดรายการหนึ่งที่เผยแพร่ที่ปลายทาง Firebase PNV JWKS ด้วยอัลกอริทึม
ES256https://fpnv.googleapis.com/v1beta/jwksการอ้างสิทธิ์ผู้ออกมีหมายเลขโปรเจ็กต์ Firebase ของคุณและอยู่ในรูปแบบต่อไปนี้
https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBERคุณดูหมายเลขโปรเจ็กต์ Firebase ได้ในแท็บ
การตั้งค่า > ทั่วไป ของคอนโซลFirebaseการอ้างสิทธิ์ผู้รับเป็นรายการที่มีหมายเลขโปรเจ็กต์ Firebase และรหัสโปรเจ็กต์ และอยู่ในรูปแบบต่อไปนี้
[ https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_NUMBER, https://fpnv.googleapis.com/projects/FIREBASE_PROJECT_ID, ]โทเค็นยังไม่หมดอายุ
โทเค็นมี Nonce ที่ถูกต้อง Nonce จะถูกต้องในกรณีต่อไปนี้
- คุณเป็นผู้สร้าง Nonce (นั่นคือ Nonce จะอยู่ในกลไกการคงอยู่ใดก็ตามที่คุณใช้)
- Nonce ยังไม่ถูกใช้
- Nonce ยังไม่หมดอายุ
ตัวอย่างเช่น การใช้งาน Express อาจมีลักษณะดังต่อไปนี้
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);
}
});