Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2800,8 +2800,19 @@ To sign up a user with passkey
```kotlin
// Using Coroutines
try {
val userData = UserData(
email = "user@example.com",
phoneNumber = "+11234567890",
name = "John Doe",
givenName = "John",
familyName = "Doe",
nickName = "johnny",
picture = "https://example.com/photo.png",
userMetadata = mapOf("signup_source" to "android_app")
)

val challenge = authenticationApiClient.signupWithPasskey(
"{user-data}",
userData,
"{realm}",
"{organization-id}"
).await()
Expand Down Expand Up @@ -2831,7 +2842,19 @@ try {
<summary>Using Java</summary>

```java
authenticationAPIClient.signupWithPasskey("{user-data}", "{realm}","{organization-id}")
UserData userData = new UserData(
"user@example.com", // email
"+11234567890", // phoneNumber
null, // userName
"John Doe", // name
"John", // givenName
"Doe", // familyName
"johnny", // nickName
"https://example.com/photo.png", // picture
Map.of("signup_source", "android_app") // userMetadata
);

authenticationAPIClient.signupWithPasskey(userData, "{realm}","{organization-id}")
.start(new Callback<PasskeyRegistrationChallenge, AuthenticationException>() {
@Override
public void onSuccess(PasskeyRegistrationChallenge result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,17 +313,24 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
*
* Example usage:
*
*
* ```
* client.signupWithPasskey("{userData}","{realm}","{organization}")
* .addParameter("scope","scope")
* .start(object: Callback<PasskeyRegistration, AuthenticationException> {
* override fun onSuccess(result: PasskeyRegistration) { }
* val userData = UserData(
* email = "user@example.com",
* name = "John Doe",
* givenName = "John",
* familyName = "Doe",
* nickName = "johnny",
* picture = "https://example.com/photo.png",
* userMetadata = mapOf("signup_source" to "android_app")
* )
* client.signupWithPasskey(userData, "{realm}", "{organization}")
* .start(object: Callback<PasskeyRegistrationChallenge, AuthenticationException> {
* override fun onSuccess(result: PasskeyRegistrationChallenge) { }
* override fun onFailure(error: AuthenticationException) { }
* })
* ```
*
* @param userData user information of the client
* @param userData user information for registration.
* @param realm the connection to use. If excluded, the application will use the default connection configured in the tenant
* @param organization id of the organization to be associated with the user while signing up
* @return a request to configure and start that will yield [PasskeyRegistrationChallenge]
Expand All @@ -333,7 +340,6 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
realm: String? = null,
organization: String? = null
): Request<PasskeyRegistrationChallenge, AuthenticationException> {
val user = gson.toJsonTree(userData)
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(PASSKEY_PATH)
.addPathSegment(REGISTER_PATH)
Expand All @@ -351,7 +357,8 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
)
val post = factory.post(url.toString(), passkeyRegistrationChallengeAdapter)
.addParameters(params) as BaseRequest<PasskeyRegistrationChallenge, AuthenticationException>
post.addParameter(USER_PROFILE_KEY, user)
post.addParameter(USER_PROFILE_KEY, gson.toJsonTree(userData.toUserProfile()))
userData.userMetadata?.let { post.addParameter(USER_METADATA_KEY, it) }
return post
}

Expand Down
14 changes: 13 additions & 1 deletion auth0/src/main/java/com/auth0/android/request/UserData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@ import com.google.gson.annotations.SerializedName
* @param phoneNumber the phone number of the user. phone number can be optional, required, or forbidden depending on the attribute configuration for the database
* @param userName the username of the user. username can be optional, required, or forbidden depending on the attribute configuration for the database
* @param name optional display name
* @param givenName the first name of the user
* @param familyName the last name of the user
* @param nickName the preferred nickname of the user
* @param picture URL pointing to the user's profile picture
* @param userMetadata additional user metadata as key-value pairs
*/
public data class UserData(
@field:SerializedName("email") val email: String? = null,
@field:SerializedName("phone_number") val phoneNumber: String? = null,
@field:SerializedName("username") val userName: String? = null,
@field:SerializedName("name") val name: String? = null,
)
@field:SerializedName("given_name") val givenName: String? = null,
@field:SerializedName("family_name") val familyName: String? = null,
@field:SerializedName("nickname") val nickName: String? = null,
@field:SerializedName("picture") val picture: String? = null,
@field:SerializedName("user_metadata") val userMetadata: Map<String, String>? = null,
) {
internal fun toUserProfile(): UserData = copy(userMetadata = null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.auth0.android.provider.JwtTestUtils
import com.auth0.android.request.HttpMethod
import com.auth0.android.request.NetworkingClient
import com.auth0.android.request.PublicKeyCredentials
import com.auth0.android.request.UserData
import com.auth0.android.request.RequestOptions
import com.auth0.android.request.ServerResponse
import com.auth0.android.request.internal.RequestFactory
Expand Down Expand Up @@ -243,7 +244,7 @@ public class AuthenticationAPIClientTest {
val auth0 = auth0
val client = AuthenticationAPIClient(auth0)
val registrationResponse = client.signupWithPasskey(
mock(),
UserData(email = "test@example.com"),
MY_CONNECTION,
"testOrganization"
)
Expand All @@ -264,6 +265,49 @@ public class AuthenticationAPIClientTest {
assertThat(registrationResponse.authSession, Matchers.comparesEqualTo(SESSION_ID))
}

@Test
public fun shouldSignupWithPasskeyWithAllUserDataFields() {
mockAPI.willReturnSuccessfulPasskeyRegistration()
val auth0 = auth0
val client = AuthenticationAPIClient(auth0)
val userData = UserData(
email = "test@example.com",
phoneNumber = "+1234567890",
userName = "testuser",
name = "Test User",
givenName = "Test",
familyName = "User",
nickName = "testy",
picture = "https://example.com/photo.png",
userMetadata = mapOf("key1" to "value1")
)
val registrationResponse = client.signupWithPasskey(
userData,
MY_CONNECTION,
"testOrganization"
).execute()
val request = mockAPI.takeRequest()
val body = bodyFromRequest<Any>(request)
assertThat(request.path, Matchers.equalTo("/passkey/register"))
assertThat(body, Matchers.hasKey("user_profile"))
@Suppress("UNCHECKED_CAST")
val userProfile = body["user_profile"] as Map<String, Any>
assertThat(userProfile, Matchers.hasEntry("email", "test@example.com"))
assertThat(userProfile, Matchers.hasEntry("phone_number", "+1234567890"))
assertThat(userProfile, Matchers.hasEntry("username", "testuser"))
assertThat(userProfile, Matchers.hasEntry("name", "Test User"))
assertThat(userProfile, Matchers.hasEntry("given_name", "Test"))
assertThat(userProfile, Matchers.hasEntry("family_name", "User"))
assertThat(userProfile, Matchers.hasEntry("nickname", "testy"))
assertThat(userProfile, Matchers.hasEntry("picture", "https://example.com/photo.png"))
assertThat(userProfile, Matchers.not(Matchers.hasKey("user_metadata")))
assertThat(body, Matchers.hasKey("user_metadata"))
@Suppress("UNCHECKED_CAST")
val metadata = body["user_metadata"] as Map<String, String>
assertThat(metadata, Matchers.hasEntry("key1", "value1"))
assertThat(registrationResponse, Matchers.`is`(Matchers.notNullValue()))
}

@Test
public fun shouldGetPasskeyChallenge() {
mockAPI.willReturnSuccessfulPasskeyChallenge()
Expand Down
Loading