Skip to content
Merged
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
52 changes: 33 additions & 19 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3201,10 +3201,12 @@ The Auth0 class can be configured with a `NetworkingClient`, which will be used
### Timeout configuration

```kotlin
val netClient = DefaultClient(
connectTimeout = 30,
readTimeout = 30
)
val netClient = DefaultClient.Builder()
.connectTimeout(30)
.readTimeout(30)
.writeTimeout(30)
.callTimeout(120)
.build()

val account = Auth0.getInstance("{YOUR_CLIENT_ID}", "{YOUR_DOMAIN}")
account.networkingClient = netClient
Expand All @@ -3214,7 +3216,12 @@ account.networkingClient = netClient
<summary>Using Java</summary>

```java
DefaultClient netClient = new DefaultClient(30, 30);
DefaultClient netClient = new DefaultClient.Builder()
.connectTimeout(30)
.readTimeout(30)
.writeTimeout(30)
.callTimeout(120)
.build();
Auth0 account = Auth0.getInstance("client id", "domain");
account.setNetworkingClient(netClient);
```
Expand All @@ -3223,23 +3230,30 @@ account.setNetworkingClient(netClient);
### Logging configuration

```kotlin
val netClient = DefaultClient(
enableLogging = true
)
val netClient = DefaultClient.Builder()
.enableLogging(true)
.build()

val account = Auth0.getInstance("{YOUR_CLIENT_ID}", "{YOUR_DOMAIN}")
account.networkingClient = netClient
```

You can also provide a custom logger to control where logs are written:

```kotlin
val netClient = DefaultClient.Builder()
.enableLogging(true)
.logger(HttpLoggingInterceptor.Logger { message -> Log.d("Auth0Http", message) })
.build()
```

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

```java
import java.util.HashMap;

DefaultClient netClient = new DefaultClient(
10, 10, new HashMap<>() ,true
);
DefaultClient netClient = new DefaultClient.Builder()
.enableLogging(true)
.build();
Auth0 account = Auth0.getInstance("client id", "domain");
account.setNetworkingClient(netClient);
```
Expand All @@ -3248,9 +3262,9 @@ account.setNetworkingClient(netClient);
### Set additional headers for all requests

```kotlin
val netClient = DefaultClient(
defaultHeaders = mapOf("{HEADER-NAME}" to "{HEADER-VALUE}")
)
val netClient = DefaultClient.Builder()
.defaultHeaders(mapOf("{HEADER-NAME}" to "{HEADER-VALUE}"))
.build()

val account = Auth0.getInstance("{YOUR_CLIENT_ID}", "{YOUR_DOMAIN}")
account.networkingClient = netClient
Expand All @@ -3263,9 +3277,9 @@ account.networkingClient = netClient
Map<String, String> defaultHeaders = new HashMap<>();
defaultHeaders.put("{HEADER-NAME}", "{HEADER-VALUE}");

DefaultClient netClient = new DefaultClient(
10,10 , defaultHeaders
);
DefaultClient netClient = new DefaultClient.Builder()
.defaultHeaders(defaultHeaders)
.build();
Auth0 account = Auth0.getInstance("client id", "domain");
account.setNetworkingClient(netClient);
```
Expand Down
29 changes: 29 additions & 0 deletions V4_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,35 @@ implementation 'com.google.code.gson:gson:2.8.9' // your preferred version

> **Note:** Pinning or excluding is not recommended long-term, as the SDK has been tested and validated against Gson 2.11.0.

### DefaultClient.Builder

v4 introduces a `DefaultClient.Builder` for configuring the HTTP client. This replaces the constructor-based approach with a more flexible builder pattern that supports additional options such as write/call timeouts, custom interceptors, and custom loggers.

**v3 (constructor-based — deprecated):**

```kotlin
// ⚠️ Deprecated: still compiles but shows a warning
val client = DefaultClient(
connectTimeout = 30,
readTimeout = 30,
enableLogging = true
)
```

**v4 (builder pattern — recommended):**

```kotlin
val client = DefaultClient.Builder()
.connectTimeout(30)
.readTimeout(30)
.writeTimeout(30)
.callTimeout(120)
.enableLogging(true)
.build()
```

The legacy constructor is deprecated but **not removed** — existing code will continue to compile and run. Your IDE will show a deprecation warning with a suggested `ReplaceWith` quick-fix to migrate to the Builder.

## Getting Help

If you encounter issues during migration:
Expand Down
204 changes: 162 additions & 42 deletions auth0/src/main/java/com/auth0/android/request/DefaultClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import okhttp3.Headers
import okhttp3.Headers.Companion.toHeaders
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
Expand All @@ -24,39 +23,181 @@ import javax.net.ssl.X509TrustManager

/**
* Default implementation of a Networking Client.
*
* Use [DefaultClient.Builder] to create a new instance with custom configuration:
*
* ```kotlin
* val client = DefaultClient.Builder()
* .connectTimeout(30)
* .readTimeout(30)
* .writeTimeout(30)
* .enableLogging(true)
* .build()
* ```
*
* The legacy constructor-based API is still supported for backward compatibility.
*/
public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal constructor(
connectTimeout: Int,
readTimeout: Int,
public class DefaultClient private constructor(
private val defaultHeaders: Map<String, String>,
enableLogging: Boolean,
private val gson: Gson,
sslSocketFactory: SSLSocketFactory?,
trustManager: X509TrustManager?
okHttpClientBuilder: OkHttpClient.Builder
) : NetworkingClient {

/**
* Create a new DefaultClient.
* Builder for creating a [DefaultClient] instance with custom configuration.
*
* @param connectTimeout the connection timeout, in seconds, to use when executing requests. Default is ten seconds.
* @param readTimeout the read timeout, in seconds, to use when executing requests. Default is ten seconds.
* @param defaultHeaders any headers that should be sent on all requests. If a specific request specifies a header with the same key as any header in the default headers, the header specified on the request will take precedence. Default is an empty map.
* @param enableLogging whether HTTP request and response info should be logged. This should only be set to `true` for debugging purposes in non-production environments, as sensitive information is included in the logs. Defaults to `false`.
* Example usage:
* ```kotlin
* val client = DefaultClient.Builder()
* .connectTimeout(30)
* .readTimeout(30)
* .writeTimeout(30)
* .callTimeout(60)
* .defaultHeaders(mapOf("X-Custom" to "value"))
* .enableLogging(true)
* .logger(myCustomLogger)
* .build()
* ```
*/
public class Builder {
private var connectTimeout: Int = DEFAULT_TIMEOUT_SECONDS
private var readTimeout: Int = DEFAULT_TIMEOUT_SECONDS
private var writeTimeout: Int = DEFAULT_TIMEOUT_SECONDS
private var callTimeout: Int = 0
private var defaultHeaders: Map<String, String> = mapOf()
private var enableLogging: Boolean = false
private var logger: HttpLoggingInterceptor.Logger? = null
private var gson: Gson = GsonProvider.gson
private var sslSocketFactory: SSLSocketFactory? = null
private var trustManager: X509TrustManager? = null

/**
* Sets the connection timeout, in seconds. Default is 10 seconds.
*/
public fun connectTimeout(timeout: Int): Builder = apply { this.connectTimeout = timeout }

/**
* Sets the read timeout, in seconds. Default is 10 seconds.
*/
public fun readTimeout(timeout: Int): Builder = apply { this.readTimeout = timeout }

/**
* Sets the write timeout, in seconds. Default is 10 seconds.
*/
public fun writeTimeout(timeout: Int): Builder = apply { this.writeTimeout = timeout }

/**
* Sets the call timeout, in seconds. Default is 0 (no timeout).
* This is an overall timeout that spans the entire call: resolving DNS, connecting,
* writing the request body, server processing, and reading the response body.
*/
public fun callTimeout(timeout: Int): Builder = apply { this.callTimeout = timeout }

/**
* Sets default headers to include on all requests. If a specific request specifies
* a header with the same key, the request-level header takes precedence.
*/
public fun defaultHeaders(headers: Map<String, String>): Builder =
apply { this.defaultHeaders = headers }

/**
* Enables or disables HTTP logging. Should only be set to `true` for debugging
* in non-production environments, as sensitive information may be logged.
* Defaults to `false`.
*/
public fun enableLogging(enable: Boolean): Builder = apply { this.enableLogging = enable }

/**
* Sets a custom logger for the HTTP logging interceptor.
* Only takes effect if [enableLogging] is set to `true`.
* If not set, the default [HttpLoggingInterceptor.Logger] (which logs to logcat) is used.
*/
public fun logger(logger: HttpLoggingInterceptor.Logger): Builder =
apply { this.logger = logger }

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun gson(gson: Gson): Builder = apply { this.gson = gson }

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun sslSocketFactory(
sslSocketFactory: SSLSocketFactory,
trustManager: X509TrustManager
): Builder = apply {
this.sslSocketFactory = sslSocketFactory
this.trustManager = trustManager
}

/**
* Builds a new [DefaultClient] instance with the configured options.
*/
public fun build(): DefaultClient {
val okBuilder = OkHttpClient.Builder()

okBuilder.addInterceptor(RetryInterceptor())

if (enableLogging) {
val loggingInterceptor = if (logger != null) {
HttpLoggingInterceptor(logger!!)
} else {
HttpLoggingInterceptor()
}
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
okBuilder.addInterceptor(loggingInterceptor)
}

okBuilder.connectTimeout(connectTimeout.toLong(), TimeUnit.SECONDS)
okBuilder.readTimeout(readTimeout.toLong(), TimeUnit.SECONDS)
okBuilder.writeTimeout(writeTimeout.toLong(), TimeUnit.SECONDS)
okBuilder.callTimeout(callTimeout.toLong(), TimeUnit.SECONDS)

val ssl = sslSocketFactory
val tm = trustManager
if (ssl != null && tm != null) {
okBuilder.sslSocketFactory(ssl, tm)
}

return DefaultClient(defaultHeaders, gson, okBuilder)
}
}

/**
* Create a new DefaultClient with default configuration.
*
* For more configuration options, use [DefaultClient.Builder].
*
* @param connectTimeout the connection timeout, in seconds. Default is 10 seconds.
* @param readTimeout the read timeout, in seconds. Default is 10 seconds.
* @param defaultHeaders headers to include on all requests. Default is an empty map.
* @param enableLogging whether to log HTTP request/response info. Defaults to `false`.
*/
@Deprecated(
message = "Use DefaultClient.Builder() for more configuration options.",
replaceWith = ReplaceWith(
"DefaultClient.Builder()" +
".connectTimeout(connectTimeout)" +
".readTimeout(readTimeout)" +
".defaultHeaders(defaultHeaders)" +
".enableLogging(enableLogging)" +
".build()"
)
)
@JvmOverloads
public constructor(
connectTimeout: Int = DEFAULT_TIMEOUT_SECONDS,
readTimeout: Int = DEFAULT_TIMEOUT_SECONDS,
defaultHeaders: Map<String, String> = mapOf(),
enableLogging: Boolean = false,
) : this(
connectTimeout,
readTimeout,
defaultHeaders,
enableLogging,
GsonProvider.gson,
null,
null
defaultHeaders = defaultHeaders,
gson = GsonProvider.gson,
okHttpClientBuilder = OkHttpClient.Builder().apply {
addInterceptor(RetryInterceptor())
if (enableLogging) {
addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
}
connectTimeout(connectTimeout.toLong(), TimeUnit.SECONDS)
readTimeout(readTimeout.toLong(), TimeUnit.SECONDS)
}
)

@get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Expand Down Expand Up @@ -125,35 +266,14 @@ public class DefaultClient @VisibleForTesting(otherwise = VisibleForTesting.PRIV
}

init {
val builder = OkHttpClient.Builder()
// Add retry interceptor
builder.addInterceptor(RetryInterceptor())

// logging
if (enableLogging) {
val logger: Interceptor = HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)
builder.addInterceptor(logger)
}

// timeouts
builder.connectTimeout(connectTimeout.toLong(), TimeUnit.SECONDS)
builder.readTimeout(readTimeout.toLong(), TimeUnit.SECONDS)
okHttpClient = okHttpClientBuilder.build()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this create a new client here ? Is this init block required now ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up. The init block now just calls okHttpClientBuilder.build() and creates the nonRetryableOkHttpClient from it. No duplicate configuration


// testing with ssl hook (internal constructor params visibility only)
if (sslSocketFactory != null && trustManager != null) {
builder.sslSocketFactory(sslSocketFactory, trustManager)
}

okHttpClient = builder.build()

// Non-retryable client for DPoP requests
// Non-retryable client for DPoP requests — inherits all configuration
nonRetryableOkHttpClient = okHttpClient.newBuilder()
.retryOnConnectionFailure(false)
.build()
}


internal companion object {
const val DEFAULT_TIMEOUT_SECONDS: Int = 10
val APPLICATION_JSON_UTF8: MediaType =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,7 @@ public class WebAuthProviderTest {

@Test
@Throws(Exception::class)
@Suppress("DEPRECATION")
public fun shouldResumeLoginWithCustomNetworkingClient() {
val networkingClient: NetworkingClient = Mockito.spy(DefaultClient())
val authCallback = mock<Callback<Credentials, AuthenticationException>>()
Expand Down
Loading
Loading