Skip to content

Commit fb51392

Browse files
committed
more passkeys but not available yet
1 parent 61de863 commit fb51392

File tree

1 file changed

+139
-67
lines changed

1 file changed

+139
-67
lines changed

auth/src/main/java/com/firebase/ui/auth/compose/credentialmanager/PasskeyAuthHandler.kt

Lines changed: 139 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)