diff --git a/EXAMPLES.md b/EXAMPLES.md
index 52ea9eae9..d26eb1bdf 100644
--- a/EXAMPLES.md
+++ b/EXAMPLES.md
@@ -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
+
+
+ Using Java
+
+```java
+MyAccountAPIClient client = new MyAccountAPIClient(auth0, accessToken).useDPoP(context);
+```
+
+
### Enroll a new passkey
**Scopes required:** `create:me:authentication_methods`
@@ -2052,7 +2076,7 @@ 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:**
@@ -2060,10 +2084,20 @@ Retrieves a detailed list of all the authentication methods that the current use
The user must have one or more authentication methods already enrolled.
```kotlin
+// Get all authentication methods
myAccountClient.getAuthenticationMethods()
.start(object : Callback, MyAccountException> {
- override fun onSuccess(result: AuthenticationMethods) {
- // List of enrolled methods in result.authenticationMethods
+ override fun onSuccess(result: List) {
+ // List of enrolled methods
+ }
+ override fun onFailure(error: MyAccountException) { }
+ })
+
+// Get authentication methods filtered by type
+myAccountClient.getAuthenticationMethods(AuthenticationMethodType.PASSKEY)
+ .start(object : Callback, MyAccountException> {
+ override fun onSuccess(result: List) {
+ // List of enrolled passkey methods only
}
override fun onFailure(error: MyAccountException) { }
})
@@ -2072,11 +2106,23 @@ myAccountClient.getAuthenticationMethods()
Using Java
```java
+// Get all authentication methods
myAccountClient.getAuthenticationMethods()
.start(new Callback, MyAccountException>() {
@Override
- public void onSuccess(AuthenticationMethods result) {
- // List of enrolled methods in result.getAuthenticationMethods()
+ public void onSuccess(List 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, MyAccountException>() {
+ @Override
+ public void onSuccess(List result) {
+ // List of enrolled passkey methods only
}
@Override
public void onFailure(@NonNull MyAccountException error) { }
diff --git a/auth0/src/main/java/com/auth0/android/myaccount/AuthenticationMethodType.kt b/auth0/src/main/java/com/auth0/android/myaccount/AuthenticationMethodType.kt
new file mode 100644
index 000000000..8f8002ba6
--- /dev/null
+++ b/auth0/src/main/java/com/auth0/android/myaccount/AuthenticationMethodType.kt
@@ -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")
+}
\ No newline at end of file
diff --git a/auth0/src/main/java/com/auth0/android/myaccount/MyAccountAPIClient.kt b/auth0/src/main/java/com/auth0/android/myaccount/MyAccountAPIClient.kt
index 0a71b1f66..14ad4b4c6 100644
--- a/auth0/src/main/java/com/auth0/android/myaccount/MyAccountAPIClient.kt
+++ b/auth0/src/main/java/com/auth0/android/myaccount/MyAccountAPIClient.kt
@@ -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
@@ -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
@@ -52,7 +54,12 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
private val accessToken: String,
private val factory: RequestFactory,
private val gson: Gson
-) {
+) : SenderConstraining {
+
+ private var dPoP: DPoP? = null
+
+ private val authorizationHeader: String
+ get() = if (dPoP != null) "DPoP $accessToken" else "Bearer $accessToken"
/**
* Creates a new MyAccountAPI client instance.
@@ -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.
@@ -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)
}
/**
@@ -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)
}
@@ -256,6 +283,9 @@ 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
*
@@ -263,7 +293,7 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
* val auth0 = Auth0.getInstance("YOUR_CLIENT_ID", "YOUR_DOMAIN")
* val apiClient = MyAccountAPIClient(auth0, accessToken)
*
- *
+ * // Get all authentication methods
* apiClient.getAuthenticationMethods()
* .start(object : Callback, MyAccountException> {
* override fun onSuccess(result: List) {
@@ -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, MyAccountException> {
+ * override fun onSuccess(result: List) {
+ * 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, MyAccountException> {
- val url = getDomainUrlBuilder().addPathSegment(AUTHENTICATION_METHODS).build()
+ @JvmOverloads
+ public fun getAuthenticationMethods(type: AuthenticationMethodType? = null): Request, MyAccountException> {
+ val url = getDomainUrlBuilder().apply {
+ addPathSegment(AUTHENTICATION_METHODS)
+ type?.let { addQueryParameter(TYPE_KEY, it.type) }
+ }.build()
val listAdapter = object : JsonAdapter> {
override fun fromJson(
@@ -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)
}
@@ -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)
}
/**
@@ -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)
}
@@ -438,8 +487,8 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
val voidAdapter = object : JsonAdapter {
override fun fromJson(reader: Reader, metadata: Map): Void? = null
}
- return factory.delete(url.toString(), voidAdapter)
- .addHeader(AUTHORIZATION_KEY, "Bearer $accessToken")
+ return factory.delete(url.toString(), voidAdapter, dPoP)
+ .addHeader(AUTHORIZATION_KEY, authorizationHeader)
}
/**
@@ -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)
}
/**
@@ -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)
}
/**
@@ -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)
}
/**
@@ -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)
}
/**
@@ -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)
}
/**
@@ -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.
@@ -790,9 +839,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
private fun buildEnrollmentRequest(params: Map): Request {
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 {
diff --git a/auth0/src/test/java/com/auth0/android/myaccount/MyAccountAPIClientTest.kt b/auth0/src/test/java/com/auth0/android/myaccount/MyAccountAPIClientTest.kt
index 2d4beb419..76a095428 100644
--- a/auth0/src/test/java/com/auth0/android/myaccount/MyAccountAPIClientTest.kt
+++ b/auth0/src/test/java/com/auth0/android/myaccount/MyAccountAPIClientTest.kt
@@ -1,6 +1,11 @@
package com.auth0.android.myaccount
+import android.content.Context
import com.auth0.android.Auth0
+import com.auth0.android.dpop.DPoPKeyStore
+import com.auth0.android.dpop.DPoPUtil
+import com.auth0.android.dpop.FakeECPrivateKey
+import com.auth0.android.dpop.FakeECPublicKey
import com.auth0.android.request.PublicKeyCredentials
import com.auth0.android.request.Response
import com.auth0.android.result.AuthenticationMethod
@@ -18,6 +23,7 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.whenever
import okhttp3.mockwebserver.RecordedRequest
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
@@ -36,11 +42,17 @@ public class MyAccountAPIClientTest {
private lateinit var client: MyAccountAPIClient
private lateinit var gson: Gson
private lateinit var mockAPI: MyAccountAPIMockServer
+ private lateinit var mockKeyStore: DPoPKeyStore
+ private lateinit var mockContext: Context
@Before
public fun setUp() {
mockAPI = MyAccountAPIMockServer()
MockitoAnnotations.openMocks(this)
+ mockKeyStore = mock()
+ mockContext = mock()
+ whenever(mockContext.applicationContext).thenReturn(mockContext)
+ DPoPUtil.keyStore = mockKeyStore
gson = GsonBuilder().serializeNulls().create()
client = MyAccountAPIClient(auth0, ACCESS_TOKEN)
}
@@ -345,6 +357,53 @@ public class MyAccountAPIClientTest {
assertThat(request.method, Matchers.equalTo("GET"))
}
+ @Test
+ public fun `getAuthenticationMethods should include type query parameter when specified`() {
+ val callback = MockMyAccountCallback>()
+ client.getAuthenticationMethods(AuthenticationMethodType.PASSKEY).start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.path, Matchers.equalTo("/me/v1/authentication-methods?type=passkey"))
+ assertThat(request.getHeader("Authorization"), Matchers.equalTo("Bearer $ACCESS_TOKEN"))
+ assertThat(request.method, Matchers.equalTo("GET"))
+ }
+
+ @Test
+ public fun `getAuthenticationMethods should not include type query parameter when null`() {
+ val callback = MockMyAccountCallback>()
+ client.getAuthenticationMethods(null).start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.path, Matchers.equalTo("/me/v1/authentication-methods"))
+ assertThat(request.method, Matchers.equalTo("GET"))
+ }
+
+ @Test
+ public fun `getAuthenticationMethods should include correct type value for each AuthenticationMethodType`() {
+ val typesToExpected = mapOf(
+ AuthenticationMethodType.PHONE to "phone",
+ AuthenticationMethodType.EMAIL to "email",
+ AuthenticationMethodType.TOTP to "totp",
+ AuthenticationMethodType.PUSH to "push-notification",
+ AuthenticationMethodType.RECOVERY_CODE to "recovery-code",
+ AuthenticationMethodType.PASSWORD to "password",
+ AuthenticationMethodType.WEBAUTHN_PLATFORM to "webauthn-platform",
+ AuthenticationMethodType.WEBAUTHN_ROAMING to "webauthn-roaming"
+ )
+
+ for ((type, expected) in typesToExpected) {
+ val callback = MockMyAccountCallback>()
+ client.getAuthenticationMethods(type).start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(
+ "type=$expected should be in query",
+ request.path,
+ Matchers.equalTo("/me/v1/authentication-methods?type=$expected")
+ )
+ }
+ }
+
@Test
public fun `getAuthenticationMethodById should build correct URL and Authorization header`() {
val callback = MockMyAccountCallback()
@@ -485,6 +544,101 @@ public class MyAccountAPIClientTest {
assertThat(body, Matchers.hasEntry("type", "push-notification" as Any))
}
+ // DPoP tests
+
+ @Test
+ public fun `should use Bearer authorization header when DPoP is not enabled`() {
+ val callback = MockMyAccountCallback>()
+ client.getFactors().start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.getHeader("Authorization"), Matchers.equalTo("Bearer $ACCESS_TOKEN"))
+ assertThat(request.getHeader("DPoP"), Matchers.nullValue())
+ }
+
+ @Test
+ public fun `should use DPoP authorization header when DPoP is enabled`() {
+ whenever(mockKeyStore.hasKeyPair()).thenReturn(true)
+ whenever(mockKeyStore.getKeyPair()).thenReturn(Pair(FakeECPrivateKey(), FakeECPublicKey()))
+
+ val dpopClient = MyAccountAPIClient(auth0, ACCESS_TOKEN).useDPoP(mockContext)
+ val callback = MockMyAccountCallback>()
+ dpopClient.getFactors().start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.getHeader("Authorization"), Matchers.equalTo("DPoP $ACCESS_TOKEN"))
+ assertThat(request.getHeader("DPoP"), Matchers.notNullValue())
+ }
+
+ @Test
+ public fun `should include DPoP proof header on POST requests when DPoP is enabled`() {
+ whenever(mockKeyStore.hasKeyPair()).thenReturn(true)
+ whenever(mockKeyStore.getKeyPair()).thenReturn(Pair(FakeECPrivateKey(), FakeECPublicKey()))
+
+ val dpopClient = MyAccountAPIClient(auth0, ACCESS_TOKEN).useDPoP(mockContext)
+ val callback = MockMyAccountCallback()
+ dpopClient.enrollEmail("test@example.com").start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.getHeader("Authorization"), Matchers.equalTo("DPoP $ACCESS_TOKEN"))
+ assertThat(request.getHeader("DPoP"), Matchers.notNullValue())
+ assertThat(request.method, Matchers.equalTo("POST"))
+ }
+
+ @Test
+ public fun `should not include DPoP proof header when DPoP is not enabled`() {
+ val callback = MockMyAccountCallback()
+ client.enrollEmail("test@example.com").start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.getHeader("Authorization"), Matchers.equalTo("Bearer $ACCESS_TOKEN"))
+ assertThat(request.getHeader("DPoP"), Matchers.nullValue())
+ }
+
+ @Test
+ public fun `should not include DPoP proof header when DPoP is enabled but no key pair exists`() {
+ whenever(mockKeyStore.hasKeyPair()).thenReturn(false)
+
+ val dpopClient = MyAccountAPIClient(auth0, ACCESS_TOKEN).useDPoP(mockContext)
+ val callback = MockMyAccountCallback>()
+ dpopClient.getFactors().start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.getHeader("Authorization"), Matchers.equalTo("DPoP $ACCESS_TOKEN"))
+ assertThat(request.getHeader("DPoP"), Matchers.nullValue())
+ }
+
+ @Test
+ public fun `should use DPoP authorization header on PATCH requests when DPoP is enabled`() {
+ whenever(mockKeyStore.hasKeyPair()).thenReturn(true)
+ whenever(mockKeyStore.getKeyPair()).thenReturn(Pair(FakeECPrivateKey(), FakeECPublicKey()))
+
+ val dpopClient = MyAccountAPIClient(auth0, ACCESS_TOKEN).useDPoP(mockContext)
+ val callback = MockMyAccountCallback()
+ dpopClient.updateAuthenticationMethodById("method|123", authenticationMethodName = "Test")
+ .start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.getHeader("Authorization"), Matchers.equalTo("DPoP $ACCESS_TOKEN"))
+ assertThat(request.getHeader("DPoP"), Matchers.notNullValue())
+ assertThat(request.method, Matchers.equalTo("PATCH"))
+ }
+
+ @Test
+ public fun `should use DPoP authorization header on DELETE requests when DPoP is enabled`() {
+ whenever(mockKeyStore.hasKeyPair()).thenReturn(true)
+ whenever(mockKeyStore.getKeyPair()).thenReturn(Pair(FakeECPrivateKey(), FakeECPublicKey()))
+
+ val dpopClient = MyAccountAPIClient(auth0, ACCESS_TOKEN).useDPoP(mockContext)
+ val callback = MockMyAccountCallback()
+ dpopClient.deleteAuthenticationMethod("method|123").start(callback)
+
+ val request = mockAPI.takeRequest()
+ assertThat(request.getHeader("Authorization"), Matchers.equalTo("DPoP $ACCESS_TOKEN"))
+ assertThat(request.getHeader("DPoP"), Matchers.notNullValue())
+ assertThat(request.method, Matchers.equalTo("DELETE"))
+ }
+
private fun bodyFromRequest(request: RecordedRequest): Map {
val mapType = object : TypeToken