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
56 changes: 51 additions & 5 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1845,6 +1845,30 @@ Use the Auth0 My Account API to manage the current user's account.

To call the My Account API, you need an access token issued specifically for this API, including any required scopes for the operations you want to perform. See [API credentials [EA]](#api-credentials-ea) to learn how to obtain one.

```kotlin
val client = MyAccountAPIClient(auth0, accessToken)
```

#### Using DPoP

If your application uses [DPoP (Demonstrating Proof of Possession)](https://auth0.com/docs/get-started/authentication-and-authorization-flow/call-your-api-using-the-authorization-code-flow-with-dpop), you can enable it on the My Account API client:

```kotlin
val client = MyAccountAPIClient(auth0, accessToken).useDPoP(context)
```

When DPoP is enabled, the client will automatically:
- Use the `DPoP` authorization scheme instead of `Bearer`
- Include a DPoP proof header on every request

<details>
<summary>Using Java</summary>

```java
MyAccountAPIClient client = new MyAccountAPIClient(auth0, accessToken).useDPoP(context);
```
</details>

### Enroll a new passkey

**Scopes required:** `create:me:authentication_methods`
Expand Down Expand Up @@ -2052,18 +2076,28 @@ myAccountClient.getFactors()
### Get All Enrolled Authentication Methods
**Scopes required:** `read:me:authentication_methods`

Retrieves a detailed list of all the authentication methods that the current user has already enrolled in.
Retrieves a detailed list of all the authentication methods that the current user has already enrolled in. You can optionally filter the results by type using `AuthenticationMethodType`.


**Prerequisites:**

The user must have one or more authentication methods already enrolled.

```kotlin
// Get all authentication methods
myAccountClient.getAuthenticationMethods()
.start(object : Callback<List<AuthenticationMethod>, MyAccountException> {
override fun onSuccess(result: AuthenticationMethods) {
// List of enrolled methods in result.authenticationMethods
override fun onSuccess(result: List<AuthenticationMethod>) {
// List of enrolled methods
}
override fun onFailure(error: MyAccountException) { }
})

// Get authentication methods filtered by type
myAccountClient.getAuthenticationMethods(AuthenticationMethodType.PASSKEY)
.start(object : Callback<List<AuthenticationMethod>, MyAccountException> {
override fun onSuccess(result: List<AuthenticationMethod>) {
// List of enrolled passkey methods only
}
override fun onFailure(error: MyAccountException) { }
})
Expand All @@ -2072,11 +2106,23 @@ myAccountClient.getAuthenticationMethods()
<summary>Using Java</summary>

```java
// Get all authentication methods
myAccountClient.getAuthenticationMethods()
.start(new Callback<List<AuthenticationMethod>, MyAccountException>() {
@Override
public void onSuccess(AuthenticationMethods result) {
// List of enrolled methods in result.getAuthenticationMethods()
public void onSuccess(List<AuthenticationMethod> result) {
// List of enrolled methods
}
@Override
public void onFailure(@NonNull MyAccountException error) { }
});

// Get authentication methods filtered by type
myAccountClient.getAuthenticationMethods(AuthenticationMethodType.PASSKEY)
.start(new Callback<List<AuthenticationMethod>, MyAccountException>() {
@Override
public void onSuccess(List<AuthenticationMethod> result) {
// List of enrolled passkey methods only
}
@Override
public void onFailure(@NonNull MyAccountException error) { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.auth0.android.myaccount

/**
* Represents the types of authentication methods supported by the My Account API.
*/
public enum class AuthenticationMethodType(public val type: String) {
PASSWORD("password"),
PASSKEY("passkey"),
TOTP("totp"),
PHONE("phone"),
EMAIL("email"),
PUSH("push-notification"),
RECOVERY_CODE("recovery-code"),
WEBAUTHN_PLATFORM("webauthn-platform"),
WEBAUTHN_ROAMING("webauthn-roaming")
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.auth0.android.myaccount

import android.content.Context
import androidx.annotation.VisibleForTesting
import com.auth0.android.Auth0
import com.auth0.android.Auth0Exception
import com.auth0.android.NetworkErrorException
import com.auth0.android.authentication.ParameterBuilder
import com.auth0.android.dpop.DPoP
import com.auth0.android.dpop.SenderConstraining
import com.auth0.android.request.ErrorAdapter
import com.auth0.android.request.JsonAdapter
import com.auth0.android.request.PublicKeyCredentials
Expand All @@ -24,7 +27,6 @@ import com.auth0.android.result.PasskeyEnrollmentChallenge
import com.auth0.android.result.PasskeyRegistrationChallenge
import com.auth0.android.result.RecoveryCodeEnrollmentChallenge
import com.auth0.android.result.TotpEnrollmentChallenge

import com.google.gson.Gson
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
Expand Down Expand Up @@ -52,7 +54,12 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
private val accessToken: String,
private val factory: RequestFactory<MyAccountException>,
private val gson: Gson
) {
) : SenderConstraining<MyAccountAPIClient> {

private var dPoP: DPoP? = null

private val authorizationHeader: String
get() = if (dPoP != null) "DPoP $accessToken" else "Bearer $accessToken"

/**
* Creates a new MyAccountAPI client instance.
Expand All @@ -75,6 +82,25 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
GsonProvider.gson
)

/**
* Enable DPoP (Demonstrating Proof of Possession) for this client.
*
* When enabled, requests will include a DPoP proof header and the Authorization header
* will use the "DPoP" scheme instead of "Bearer".
*
* Example usage:
* ```kotlin
* val client = MyAccountAPIClient(auth0, accessToken).useDPoP(context)
* ```
*
* @param context the Android context
* @return this client instance for chaining
*/
override fun useDPoP(context: Context): MyAccountAPIClient {
dPoP = DPoP(context)
return this
}


/**
* Requests a challenge for enrolling a new passkey. This is the first part of the enrollment flow.
Expand Down Expand Up @@ -171,9 +197,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
)
}
}
return factory.post(url.toString(), passkeyEnrollmentAdapter)
return factory.post(url.toString(), passkeyEnrollmentAdapter, dPoP)
.addParameters(params)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

/**
Expand Down Expand Up @@ -239,11 +265,12 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting

return factory.post(
url.toString(),
GsonAdapter(PasskeyAuthenticationMethod::class.java, gson)
GsonAdapter(PasskeyAuthenticationMethod::class.java, gson),
dPoP
)
.addParameters(params)
.addParameter(AUTHN_RESPONSE_KEY, authnResponse)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}


Expand All @@ -256,14 +283,17 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
* [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access).
* Please reach out to Auth0 support to get it enabled for your tenant.
*
* ## Scopes Required
*
* `read:me:authentication_methods`
*
* ## Usage
*
* ```kotlin
* val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN")
* val apiClient = MyAccountAPIClient(auth0, accessToken)
*
*
* // Get all authentication methods
* apiClient.getAuthenticationMethods()
* .start(object : Callback<List<AuthenticationMethod>, MyAccountException> {
* override fun onSuccess(result: List<AuthenticationMethod>) {
Expand All @@ -274,11 +304,30 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
* Log.e("MyApp", "Failed with: ${error.message}")
* }
* })
*
* // Get authentication methods filtered by type
* apiClient.getAuthenticationMethods(AuthenticationMethodType.PASSKEY)
* .start(object : Callback<List<AuthenticationMethod>, MyAccountException> {
* override fun onSuccess(result: List<AuthenticationMethod>) {
* Log.d("MyApp", "Passkey methods: $result")
* }
*
* override fun onFailure(error: MyAccountException) {
* Log.e("MyApp", "Failed with: ${error.message}")
* }
* })
* ```
*
* @param type Optional filter to retrieve only authentication methods of a specific type.
* @return A request to get the list of authentication methods.
*
*/
public fun getAuthenticationMethods(): Request<List<AuthenticationMethod>, MyAccountException> {
val url = getDomainUrlBuilder().addPathSegment(AUTHENTICATION_METHODS).build()
@JvmOverloads
public fun getAuthenticationMethods(type: AuthenticationMethodType? = null): Request<List<AuthenticationMethod>, MyAccountException> {
val url = getDomainUrlBuilder().apply {
addPathSegment(AUTHENTICATION_METHODS)
type?.let { addQueryParameter(TYPE_KEY, it.type) }
}.build()

val listAdapter = object : JsonAdapter<List<AuthenticationMethod>> {
override fun fromJson(
Expand All @@ -289,8 +338,8 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
return container.authenticationMethods
}
}
return factory.get(url.toString(), listAdapter)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
return factory.get(url.toString(), listAdapter, dPoP)
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}


Expand Down Expand Up @@ -331,8 +380,8 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
.addPathSegment(AUTHENTICATION_METHODS)
.addPathSegment(authenticationMethodId)
.build()
return factory.get(url.toString(), GsonAdapter(AuthenticationMethod::class.java, gson))
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
return factory.get(url.toString(), GsonAdapter(AuthenticationMethod::class.java, gson), dPoP)
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

/**
Expand Down Expand Up @@ -390,9 +439,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
}
}.asDictionary()

return factory.patch(url.toString(), GsonAdapter(AuthenticationMethod::class.java, gson))
return factory.patch(url.toString(), GsonAdapter(AuthenticationMethod::class.java, gson), dPoP)
.addParameters(params)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}


Expand Down Expand Up @@ -438,8 +487,8 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
val voidAdapter = object : JsonAdapter<Void?> {
override fun fromJson(reader: Reader, metadata: Map<String, Any>): Void? = null
}
return factory.delete(url.toString(), voidAdapter)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
return factory.delete(url.toString(), voidAdapter, dPoP)
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

/**
Expand Down Expand Up @@ -475,8 +524,8 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
return container.factors
}
}
return factory.get(url.toString(), listAdapter)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
return factory.get(url.toString(), listAdapter, dPoP)
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

/**
Expand Down Expand Up @@ -579,9 +628,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
val params = ParameterBuilder.newBuilder().set(TYPE_KEY, "totp").asDictionary()
val url = getDomainUrlBuilder().addPathSegment(AUTHENTICATION_METHODS).build()
val adapter = GsonAdapter(TotpEnrollmentChallenge::class.java, gson)
return factory.post(url.toString(), adapter)
return factory.post(url.toString(), adapter, dPoP)
.addParameters(params)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

/**
Expand Down Expand Up @@ -612,9 +661,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
val url = getDomainUrlBuilder().addPathSegment(AUTHENTICATION_METHODS).build()
// The response structure for push notification challenge is the same as TOTP (contains barcode_uri)
val adapter = GsonAdapter(TotpEnrollmentChallenge::class.java, gson)
return factory.post(url.toString(), adapter)
return factory.post(url.toString(), adapter, dPoP)
.addParameters(params)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

/**
Expand Down Expand Up @@ -644,9 +693,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
val params = ParameterBuilder.newBuilder().set(TYPE_KEY, "recovery-code").asDictionary()
val url = getDomainUrlBuilder().addPathSegment(AUTHENTICATION_METHODS).build()
val adapter = GsonAdapter(RecoveryCodeEnrollmentChallenge::class.java, gson)
return factory.post(url.toString(), adapter)
return factory.post(url.toString(), adapter, dPoP)
.addParameters(params)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

/**
Expand Down Expand Up @@ -687,9 +736,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
.addPathSegment(VERIFY)
.build()
val params = mapOf("otp_code" to otpCode, AUTH_SESSION_KEY to authSession)
return factory.post(url.toString(), GsonAdapter(AuthenticationMethod::class.java, gson))
return factory.post(url.toString(), GsonAdapter(AuthenticationMethod::class.java, gson), dPoP)
.addParameters(params)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

/**
Expand Down Expand Up @@ -727,9 +776,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
.addPathSegment(VERIFY)
.build()
val params = mapOf(AUTH_SESSION_KEY to authSession)
return factory.post(url.toString(), GsonAdapter(AuthenticationMethod::class.java, gson))
return factory.post(url.toString(), GsonAdapter(AuthenticationMethod::class.java, gson), dPoP)
.addParameters(params)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

// WebAuthn methods are private.
Expand Down Expand Up @@ -790,9 +839,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting

private fun buildEnrollmentRequest(params: Map<String, String>): Request<EnrollmentChallenge, MyAccountException> {
val url = getDomainUrlBuilder().addPathSegment(AUTHENTICATION_METHODS).build()
return factory.post(url.toString(), GsonAdapter(EnrollmentChallenge::class.java, gson))
return factory.post(url.toString(), GsonAdapter(EnrollmentChallenge::class.java, gson), dPoP)
.addParameters(params)
.addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
.addHeader(AUTHORIZATION_KEY, authorizationHeader)
}

private fun getDomainUrlBuilder(): HttpUrl.Builder {
Expand Down
Loading
Loading