11package com.auth0.android.request
22
33import androidx.annotation.VisibleForTesting
4+ import com.auth0.android.dpop.DPoPUtil
45import com.auth0.android.request.internal.GsonProvider
56import com.google.gson.Gson
6- import okhttp3.*
7+ import okhttp3.Call
8+ import okhttp3.Headers
79import okhttp3.Headers.Companion.toHeaders
10+ import okhttp3.HttpUrl
811import okhttp3.HttpUrl.Companion.toHttpUrl
12+ import okhttp3.Interceptor
13+ import okhttp3.MediaType
914import okhttp3.MediaType.Companion.toMediaType
15+ import okhttp3.OkHttpClient
1016import okhttp3.Request
1117import okhttp3.RequestBody.Companion.toRequestBody
1218import 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
0 commit comments