@@ -48,17 +48,33 @@ import org.json.JSONObject
4848 * This class provides methods to register and authenticate users with passkeys (WebAuthn credentials),
4949 * integrating with both Android's Credential Manager and Firebase Authentication.
5050 *
51+ * **IMPORTANT - Production Requirements:**
52+ *
53+ * Firebase Auth does not have native passkey support yet. To use passkeys with Firebase in production,
54+ * you MUST implement a server-side component that:
55+ * 1. Generates WebAuthn challenges
56+ * 2. Verifies passkey registration/authentication responses
57+ * 3. Creates Firebase custom tokens after successful verification
58+ * 4. Returns the custom token to your client
59+ *
60+ * The [linkPasskeyToFirebase] and [signInWithPasskey] methods are currently incomplete and will throw
61+ * errors until you implement the server-side credential verification logic.
62+ *
5163 * **Registration Flow:**
52- * 1. Server generates a challenge and options
53- * 2. Call [registerPasskey] with user ID and display name
54- * 3. User interacts with biometric/device authentication
55- * 4. Credential is created and can be linked to Firebase
64+ * 1. Your server generates a challenge and options
65+ * 2. Call [registerPasskey] with the server-provided challenge
66+ * 3. User authenticates with biometric/device
67+ * 4. Send response to your server for verification
68+ * 5. Server creates a Firebase custom token
69+ * 6. Use the custom token to link or sign in to Firebase
5670 *
5771 * **Authentication Flow:**
58- * 1. Server generates a challenge
59- * 2. Call [authenticateWithPasskey]
72+ * 1. Your server generates a challenge
73+ * 2. Call [authenticateWithPasskey] with the challenge
6074 * 3. User authenticates with biometric/device
61- * 4. Returns Firebase credential for sign-in
75+ * 4. Send response to your server for verification
76+ * 5. Server creates a Firebase custom token
77+ * 6. Use the custom token to sign in to Firebase
6278 *
6379 * @property context The Android context for credential operations
6480 * @property firebaseAuth The Firebase Auth instance
@@ -254,15 +270,19 @@ class PasskeyAuthHandler(
254270 /* *
255271 * Links a passkey credential to the current Firebase user.
256272 *
257- * This method takes the response from a successful passkey registration and links it
258- * to the currently signed-in Firebase user account.
273+ * **TODO: This method requires server-side implementation.**
274+ *
275+ * This method is currently incomplete. To make it work, you need to:
276+ * 1. Implement server-side verification of the passkey registration response
277+ * 2. Have your server create a Firebase custom token after verification
278+ * 3. Complete the [createFirebaseCredentialFromPasskey] method below
259279 *
260- * **Note:** This requires the user to be already authenticated with Firebase.
280+ * Once implemented, this will link the passkey to the currently signed-in Firebase user .
261281 *
262282 * @param credentialResponse The response from [registerPasskey]
263283 * @return The updated [FirebaseUser] with the linked credential
264284 * @throws AuthException.InvalidCredentialsException if no user is signed in
265- * @throws AuthException if the linking operation fails
285+ * @throws AuthException.UnknownException currently always throws until implemented
266286 */
267287 suspend fun linkPasskeyToFirebase (
268288 credentialResponse : CreatePublicKeyCredentialResponse
@@ -273,10 +293,9 @@ class PasskeyAuthHandler(
273293 )
274294
275295 try {
276- // Extract the credential from the response
296+ // TODO: Implement server-side verification flow
277297 val authCredential = createFirebaseCredentialFromPasskey(credentialResponse)
278298
279- // Link the credential to the current user
280299 val authResult = currentUser.linkWithCredential(authCredential).await()
281300
282301 return authResult.user
@@ -295,17 +314,24 @@ class PasskeyAuthHandler(
295314 /* *
296315 * Signs in to Firebase using a passkey authentication result.
297316 *
298- * This method takes the credential from a successful passkey authentication and
299- * uses it to sign in to Firebase.
317+ * **TODO: This method requires server-side implementation.**
318+ *
319+ * This method is currently incomplete. To make it work, you need to:
320+ * 1. Implement server-side verification of the passkey authentication assertion
321+ * 2. Have your server create a Firebase custom token after verification
322+ * 3. Complete the [createFirebaseCredentialFromPasskeyAuth] method below
323+ *
324+ * Once implemented, this will sign in the user to Firebase using their passkey.
300325 *
301326 * @param publicKeyCredential The credential from [authenticateWithPasskey]
302327 * @return The signed-in [FirebaseUser]
303- * @throws AuthException if the sign-in operation fails
328+ * @throws AuthException.UnknownException currently always throws until implemented
304329 */
305330 suspend fun signInWithPasskey (
306331 publicKeyCredential : PublicKeyCredential
307332 ): FirebaseUser {
308333 try {
334+ // TODO: Implement server-side verification flow
309335 val authCredential = createFirebaseCredentialFromPasskeyAuth(publicKeyCredential)
310336
311337 val authResult = firebaseAuth.signInWithCredential(authCredential).await()
@@ -324,59 +350,98 @@ class PasskeyAuthHandler(
324350 }
325351
326352 /* *
327- * Creates a Firebase credential from a passkey registration response.
353+ * Converts a passkey registration response to a Firebase credential.
354+ *
355+ * **TODO: Implement this method with your server-side verification.**
356+ *
357+ * To complete this method, you need to:
358+ * 1. Extract the credential registration data from the response:
359+ * ```kotlin
360+ * val registrationResponseJson = response.registrationResponseJson
361+ * ```
362+ * 2. Send this JSON to your server endpoint (e.g., POST to /api/verify-passkey-registration)
363+ * 3. On your server:
364+ * - Verify the WebAuthn registration response using a library like @simplewebauthn/server
365+ * - Store the credential ID and public key for the user
366+ * - Create a Firebase custom token using the Firebase Admin SDK
367+ * - Return the custom token to the client
368+ * 4. Create a Firebase credential using the custom token:
369+ * ```kotlin
370+ * return FirebaseAuth.getInstance().signInWithCustomToken(customToken).await().user
371+ * ```
328372 *
329- * This is an internal helper method that converts the Android Credential Manager
330- * response into a format suitable for Firebase Authentication.
373+ * @param response The passkey registration response from Android Credential Manager
374+ * @return Firebase AuthCredential that can be used to link or sign in
375+ * @throws AuthException.UnknownException until this method is properly implemented
331376 */
332377 private fun createFirebaseCredentialFromPasskey (
333378 response : CreatePublicKeyCredentialResponse
334379 ): AuthCredential {
335- // This is a placeholder implementation.
336- // In a real implementation, you would need to:
337- // 1. Extract the registration response data
338- // 2. Send it to your server for verification
339- // 3. Have the server create a Firebase custom token
340- // 4. Use the custom token to create an AuthCredential
341- throw NotImplementedError (
342- " Passkey to Firebase credential conversion requires server-side implementation"
380+ // TODO: Replace this with your server verification implementation
381+ throw AuthException .UnknownException (
382+ message = " Passkey-to-Firebase credential conversion is not yet implemented. " +
383+ " You must set up server-side WebAuthn verification and Firebase custom token generation. " +
384+ " See the method documentation for implementation details."
343385 )
344386 }
345387
346388 /* *
347- * Creates a Firebase credential from a passkey authentication result .
389+ * Converts a passkey authentication response to a Firebase credential .
348390 *
349- * This is an internal helper method that converts the passkey authentication
350- * assertion into a format suitable for Firebase Authentication.
391+ * **TODO: Implement this method with your server-side verification.**
392+ *
393+ * To complete this method, you need to:
394+ * 1. Extract the authentication assertion from the credential:
395+ * ```kotlin
396+ * val authenticationResponseJson = credential.authenticationResponseJson
397+ * ```
398+ * 2. Send this JSON to your server endpoint (e.g., POST to /api/verify-passkey-auth)
399+ * 3. On your server:
400+ * - Verify the WebAuthn authentication assertion using a library like @simplewebauthn/server
401+ * - Validate the signature against the stored public key
402+ * - Create a Firebase custom token using the Firebase Admin SDK
403+ * - Return the custom token to the client
404+ * 4. Create a Firebase credential using the custom token:
405+ * ```kotlin
406+ * return FirebaseAuth.getInstance().signInWithCustomToken(customToken).await().user
407+ * ```
408+ *
409+ * @param credential The passkey authentication credential from Android Credential Manager
410+ * @return Firebase AuthCredential that can be used to sign in
411+ * @throws AuthException.UnknownException until this method is properly implemented
351412 */
352413 private fun createFirebaseCredentialFromPasskeyAuth (
353414 credential : PublicKeyCredential
354415 ): AuthCredential {
355- // This is a placeholder implementation.
356- // In a real implementation, you would need to:
357- // 1. Extract the authentication assertion
358- // 2. Send it to your server for verification
359- // 3. Have the server create a Firebase custom token
360- // 4. Use the custom token to create an AuthCredential
361- throw NotImplementedError (
362- " Passkey authentication to Firebase credential conversion requires server-side implementation"
416+ // TODO: Replace this with your server verification implementation
417+ throw AuthException .UnknownException (
418+ message = " Passkey authentication-to-Firebase credential conversion is not yet implemented. " +
419+ " You must set up server-side WebAuthn verification and Firebase custom token generation. " +
420+ " See the method documentation for implementation details."
363421 )
364422 }
365423
366424 companion object {
367425 /* *
368426 * Creates a JSON request for passkey registration.
369427 *
370- * This is a helper method to generate the required JSON format for passkey registration.
371- * In a production app, this should be generated by your server.
428+ * **WARNING: For testing/development only. Production apps should generate this on the server.**
429+ *
430+ * This helper method generates a WebAuthn PublicKeyCredentialCreationOptions JSON.
431+ * However, in production:
432+ * - The challenge MUST be generated on your server with cryptographically secure randomness
433+ * - The server should persist the challenge to verify the response later
434+ * - The server controls security parameters (timeout, attestation requirements, etc.)
435+ *
436+ * This client-side method is provided for testing and prototyping only.
372437 *
373- * @param challenge The base64 -encoded challenge from your server
374- * @param userId The user's unique identifier
375- * @param userName The user's username (typically email)
376- * @param displayName The user 's display name
377- * @param rpId The Relying Party ID (typically your domain)
378- * @param rpName The Relying Party name
379- * @return JSON string suitable for [registerPasskey]
438+ * @param challenge Base64 -encoded challenge (should come from your server)
439+ * @param userId Unique user identifier
440+ * @param userName Username (typically email address )
441+ * @param displayName User 's display name
442+ * @param rpId Relying Party ID (your domain, e.g., "example.com" )
443+ * @param rpName Relying Party name (your app name)
444+ * @return WebAuthn PublicKeyCredentialCreationOptions as JSON string
380445 */
381446 @JvmStatic
382447 fun createRegistrationRequestJson (
@@ -387,7 +452,7 @@ class PasskeyAuthHandler(
387452 rpId : String ,
388453 rpName : String
389454 ): String {
390- val request = JSONObject ().apply {
455+ return JSONObject ().apply {
391456 put(" challenge" , challenge)
392457 put(" rp" , JSONObject ().apply {
393458 put(" name" , rpName)
@@ -399,49 +464,56 @@ class PasskeyAuthHandler(
399464 put(" displayName" , displayName)
400465 })
401466 put(" pubKeyCredParams" , org.json.JSONArray ().apply {
467+ // ES256 (preferred) - ECDSA with SHA-256
402468 put(JSONObject ().apply {
403469 put(" type" , " public-key" )
404- put(" alg" , - 7 ) // ES256
470+ put(" alg" , - 7 )
405471 })
472+ // RS256 (fallback) - RSASSA-PKCS1-v1_5 with SHA-256
406473 put(JSONObject ().apply {
407474 put(" type" , " public-key" )
408- put(" alg" , - 257 ) // RS256
475+ put(" alg" , - 257 )
409476 })
410477 })
411- put(" timeout" , 60000 )
412- put(" attestation" , " none" )
478+ put(" timeout" , 60000 ) // 60 seconds
479+ put(" attestation" , " none" ) // Don't require attestation for privacy
413480 put(" authenticatorSelection" , JSONObject ().apply {
414- put(" authenticatorAttachment" , " platform" )
415- put(" requireResidentKey" , true )
481+ put(" authenticatorAttachment" , " platform" ) // Device-bound passkey
482+ put(" requireResidentKey" , true ) // Discoverable credential
416483 put(" residentKey" , " required" )
417- put(" userVerification" , " required" )
484+ put(" userVerification" , " required" ) // Biometric or PIN required
418485 })
419- }
420- return request.toString()
486+ }.toString()
421487 }
422488
423489 /* *
424490 * Creates a JSON request for passkey authentication.
425491 *
426- * This is a helper method to generate the required JSON format for passkey authentication.
427- * In a production app, this should be generated by your server.
492+ * **WARNING: For testing/development only. Production apps should generate this on the server.**
493+ *
494+ * This helper method generates a WebAuthn PublicKeyCredentialRequestOptions JSON.
495+ * However, in production:
496+ * - The challenge MUST be generated on your server with cryptographically secure randomness
497+ * - The server should persist the challenge to verify the response later
498+ * - The server may want to specify allowed credentials for better UX
428499 *
429- * @param challenge The base64-encoded challenge from your server
430- * @param rpId The Relying Party ID (typically your domain)
431- * @return JSON string suitable for [authenticateWithPasskey]
500+ * This client-side method is provided for testing and prototyping only.
501+ *
502+ * @param challenge Base64-encoded challenge (should come from your server)
503+ * @param rpId Relying Party ID (your domain, e.g., "example.com")
504+ * @return WebAuthn PublicKeyCredentialRequestOptions as JSON string
432505 */
433506 @JvmStatic
434507 fun createAuthenticationRequestJson (
435508 challenge : String ,
436509 rpId : String
437510 ): String {
438- val request = JSONObject ().apply {
511+ return JSONObject ().apply {
439512 put(" challenge" , challenge)
440- put(" timeout" , 60000 )
513+ put(" timeout" , 60000 ) // 60 seconds
441514 put(" rpId" , rpId)
442- put(" userVerification" , " required" )
443- }
444- return request.toString()
515+ put(" userVerification" , " required" ) // Biometric or PIN required
516+ }.toString()
445517 }
446518 }
447519}
0 commit comments