Skip to content

Commit 6aa2f7e

Browse files
authored
fix: Prevent DPoP replay protection error due to OkHttp retry (#902)
1 parent 0badec7 commit 6aa2f7e

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

auth0/src/main/java/com/auth0/android/request/DefaultClient.kt

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package com.auth0.android.request
22

33
import androidx.annotation.VisibleForTesting
4+
import com.auth0.android.dpop.DPoPUtil
45
import com.auth0.android.request.internal.GsonProvider
56
import com.google.gson.Gson
6-
import okhttp3.*
7+
import okhttp3.Call
8+
import okhttp3.Headers
79
import okhttp3.Headers.Companion.toHeaders
10+
import okhttp3.HttpUrl
811
import okhttp3.HttpUrl.Companion.toHttpUrl
12+
import okhttp3.Interceptor
13+
import okhttp3.MediaType
914
import okhttp3.MediaType.Companion.toMediaType
15+
import okhttp3.OkHttpClient
1016
import okhttp3.Request
1117
import okhttp3.RequestBody.Companion.toRequestBody
1218
import okhttp3.logging.HttpLoggingInterceptor
@@ -56,6 +62,12 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV
5662
@get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
5763
internal val okHttpClient: OkHttpClient
5864

65+
// Using another client to prevent OkHttp from retrying network calls especially when using DPoP with replay protection mechanism.
66+
// https://auth0team.atlassian.net/browse/ESD-56048.
67+
// TODO: This should be replaced with the chain.retryOnConnectionFailure() API when we update to OkHttp 5+
68+
@get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
69+
internal val nonRetryableOkHttpClient: OkHttpClient
70+
5971
@Throws(IllegalArgumentException::class, IOException::class)
6072
override fun load(url: String, options: RequestOptions): ServerResponse {
6173
val response = prepareCall(url.toHttpUrl(), options).execute()
@@ -90,12 +102,31 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV
90102
.url(urlBuilder.build())
91103
.headers(headers)
92104
.build()
93-
return okHttpClient.newCall(request)
105+
106+
// Use non-retryable client for DPoP requests
107+
val client = if (shouldUseNonRetryableClient(headers)) {
108+
nonRetryableOkHttpClient
109+
} else {
110+
okHttpClient
111+
}
112+
113+
return client.newCall(request)
114+
}
115+
116+
/**
117+
* Determines if the request should use the non-retryable OkHttpClient.
118+
* Returns true for:
119+
* 1. Requests with DPoP header
120+
*/
121+
private fun shouldUseNonRetryableClient(
122+
headers: Headers
123+
): Boolean {
124+
return headers[DPoPUtil.DPOP_HEADER] != null
94125
}
95126

96127
init {
97-
// client setup
98128
val builder = OkHttpClient.Builder()
129+
// Add retry interceptor
99130
builder.addInterceptor(RetryInterceptor())
100131

101132
// logging
@@ -115,6 +146,11 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV
115146
}
116147

117148
okHttpClient = builder.build()
149+
150+
// Non-retryable client for DPoP requests
151+
nonRetryableOkHttpClient = okHttpClient.newBuilder()
152+
.retryOnConnectionFailure(false)
153+
.build()
118154
}
119155

120156

auth0/src/test/java/com/auth0/android/request/DefaultClientTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,43 @@ public class DefaultClientTest {
230230
requestAssertions(sentRequest, HttpMethod.PATCH)
231231
}
232232

233+
@Test
234+
public fun shouldHaveNonRetryableClientConfigured() {
235+
val client = createDefaultClientForTest(mapOf())
236+
237+
assertThat(client.okHttpClient, notNullValue())
238+
assertThat(client.nonRetryableOkHttpClient, notNullValue())
239+
240+
assertThat(client.okHttpClient.retryOnConnectionFailure, equalTo(true))
241+
assertThat(client.nonRetryableOkHttpClient.retryOnConnectionFailure, equalTo(false))
242+
}
243+
244+
@Test
245+
public fun shouldShareSameConfigBetweenClients() {
246+
val client = createDefaultClientForTest(mapOf())
247+
248+
assertThat(
249+
client.okHttpClient.interceptors.size,
250+
equalTo(client.nonRetryableOkHttpClient.interceptors.size)
251+
)
252+
253+
assertThat(
254+
client.okHttpClient.interceptors[0] is RetryInterceptor,
255+
equalTo(true)
256+
)
257+
assertThat(
258+
client.nonRetryableOkHttpClient.interceptors[0] is RetryInterceptor,
259+
equalTo(true)
260+
)
261+
assertThat(
262+
client.okHttpClient.connectTimeoutMillis,
263+
equalTo(client.nonRetryableOkHttpClient.connectTimeoutMillis)
264+
)
265+
assertThat(
266+
client.okHttpClient.readTimeoutMillis,
267+
equalTo(client.nonRetryableOkHttpClient.readTimeoutMillis)
268+
)
269+
}
233270

234271
//Helper methods
235272
private fun requestAssertions(

0 commit comments

Comments
 (0)