Skip to content

Commit b51c5be

Browse files
authored
feat: allow configuring Auth0.Android's native networking client (#1637)
1 parent be14b33 commit b51c5be

15 files changed

Lines changed: 502 additions & 16 deletions

File tree

.github/workflows/main.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,27 @@ jobs:
3131
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f
3232
with:
3333
directory: coverage
34+
35+
android-test:
36+
name: Run Android tests
37+
runs-on: ubuntu-latest
38+
39+
steps:
40+
- name: Checkout
41+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
42+
43+
- name: Setup
44+
uses: ./.github/actions/setup
45+
46+
- name: Set up JDK 17
47+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
48+
with:
49+
distribution: temurin
50+
java-version: '17'
51+
52+
- name: Setup Gradle
53+
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5
54+
55+
- name: Run Android unit tests
56+
working-directory: example/android
57+
run: ./gradlew :react-native-auth0:testDebugUnitTest

EXAMPLES.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
- [Using Retry with Auth0 Class](#using-retry-with-auth0-class)
2222
- [Platform Support](#platform-support)
2323
- [Error Handling](#error-handling)
24+
- [Android Networking Configuration](#android-networking-configuration)
25+
- [Using Networking Options with Hooks](#using-networking-options-with-hooks)
26+
- [Using Networking Options with Auth0 Class](#using-networking-options-with-auth0-class)
2427
- [IPSIE Session Expiry](#ipsie-session-expiry)
2528
- [Biometric Authentication](#biometric-authentication)
2629
- [Biometric Policy Types](#biometric-policy-types)
@@ -637,6 +640,66 @@ function MyComponent() {
637640
2. **Configure adequate overlap period**: Ensure your Auth0 tenant has at least 180 seconds token overlap configured
638641
3. **Test on real devices**: Simulate network instability during testing to validate retry behavior
639642
643+
## Android Networking Configuration
644+
645+
> **Platform Support:** Android only. Accepted on iOS for API compatibility but has no effect.
646+
647+
The `networkingOptions` configuration option lets you tune the native networking client (`DefaultClient` from Auth0.Android's OkHttp-based stack) used for every request the native SDK makes on your behalf — web auth token exchange, credential renewal, MFA, passkeys, and My Account API calls.
648+
649+
```ts
650+
networkingOptions?: {
651+
connectTimeout?: number; // seconds, default 10
652+
readTimeout?: number; // seconds, default 10
653+
writeTimeout?: number; // seconds, default 10
654+
callTimeout?: number; // seconds, default 0 (no limit)
655+
defaultHeaders?: Record<string, string>; // sent on every request, default {}
656+
enableLogging?: boolean; // default false
657+
};
658+
```
659+
660+
Any option you omit falls back to Auth0.Android's own default.
661+
662+
> [!WARNING]
663+
> `enableLogging` is **debug-only**. When enabled, Auth0.Android logs full HTTP request and response bodies to Logcat — including access, refresh, and ID tokens returned from token-endpoint calls, in plaintext. Never enable it in a production build.
664+
665+
### Using Networking Options with Hooks
666+
667+
```jsx
668+
import React from 'react';
669+
import { Auth0Provider } from 'react-native-auth0';
670+
671+
function App() {
672+
return (
673+
<Auth0Provider
674+
domain="YOUR_AUTH0_DOMAIN"
675+
clientId="YOUR_AUTH0_CLIENT_ID"
676+
networkingOptions={{
677+
connectTimeout: 30,
678+
readTimeout: 30,
679+
defaultHeaders: { 'X-App-Version': '1.2.3' },
680+
}}
681+
>
682+
<MyComponent />
683+
</Auth0Provider>
684+
);
685+
}
686+
```
687+
688+
### Using Networking Options with Auth0 Class
689+
690+
```js
691+
import Auth0 from 'react-native-auth0';
692+
693+
const auth0 = new Auth0({
694+
domain: 'YOUR_AUTH0_DOMAIN',
695+
clientId: 'YOUR_AUTH0_CLIENT_ID',
696+
networkingOptions: {
697+
connectTimeout: 30,
698+
readTimeout: 30,
699+
},
700+
});
701+
```
702+
640703
## IPSIE Session Expiry
641704
642705
> **Platform Support:** iOS, Android, and Web.

android/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ dependencies {
7979
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
8080
implementation "androidx.browser:browser:1.10.0"
8181
implementation 'com.auth0.android:auth0:4.0.1'
82+
83+
testImplementation 'junit:junit:4.13.2'
84+
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
8285
}
8386

8487
react {

android/src/main/java/com/auth0/react/A0Auth0Module.kt

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.auth0.react
22

33
import android.app.Activity
44
import android.content.Intent
5+
import android.content.pm.ApplicationInfo
56
import android.os.Build
67
import android.os.Handler
78
import android.os.Looper
@@ -20,6 +21,7 @@ import com.auth0.android.dpop.DPoPException
2021
import com.auth0.android.provider.BrowserPicker
2122
import com.auth0.android.provider.CustomTabsOptions
2223
import com.auth0.android.provider.WebAuthProvider
24+
import com.auth0.android.request.DefaultClient
2325
import com.auth0.android.request.PublicKeyCredentials
2426
import com.auth0.android.request.UserData
2527
import com.auth0.android.result.APICredentials
@@ -64,6 +66,40 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
6466
private const val DPOP_INVALID_TOKEN_TYPE_CODE = "DPOP_INVALID_TOKEN_TYPE"
6567
private const val DPOP_MISSING_PARAMETER_CODE = "DPOP_MISSING_PARAMETER"
6668
private const val DPOP_CLEAR_KEY_FAILED_CODE = "DPOP_CLEAR_KEY_FAILED"
69+
70+
// Builds the DefaultClient Auth0.Android uses for every request it makes (web auth
71+
// token exchange, credential renewal, MFA, passkeys, etc.). Unset keys fall through to
72+
// Auth0.Android's own Builder defaults. `enableLogging` is debug-only: Auth0.Android logs
73+
// full request/response bodies (including tokens) at that level, so we never call
74+
// `logger(...)` ourselves, never expose the raw HttpLoggingInterceptor.Logger to JS, and
75+
// ignore the option entirely unless the host app is a debug build.
76+
internal fun buildNetworkingClient(options: ReadableMap, isDebuggable: Boolean): DefaultClient {
77+
val builder = DefaultClient.Builder()
78+
if (options.hasKey("connectTimeout")) builder.connectTimeout(options.getInt("connectTimeout"))
79+
if (options.hasKey("readTimeout")) builder.readTimeout(options.getInt("readTimeout"))
80+
if (options.hasKey("writeTimeout")) builder.writeTimeout(options.getInt("writeTimeout"))
81+
if (options.hasKey("callTimeout")) builder.callTimeout(options.getInt("callTimeout"))
82+
options.getMap("defaultHeaders")?.let { headers ->
83+
builder.defaultHeaders(headers.toHashMap().mapValues { it.value?.toString() ?: "" })
84+
}
85+
// Only honor enableLogging on debug builds: Auth0.Android logs full request/response
86+
// bodies at this level, including plaintext access/refresh/ID tokens from token-endpoint
87+
// responses. Test coverage in A0Auth0ModuleNetworkingOptionsTest ensures this gate holds.
88+
if (isDebuggable && options.hasKey("enableLogging")) {
89+
builder.enableLogging(options.getBoolean("enableLogging"))
90+
}
91+
return builder.build()
92+
}
93+
94+
// Auth0.getInstance() returns a shared singleton per clientId/domain: a sibling client
95+
// (or this same client on re-init) must not inherit another initialization's networking
96+
// config, so this always resolves to a fresh DefaultClient() when options are absent
97+
// rather than leaving the previous networkingClient in place.
98+
internal fun resolveNetworkingClient(
99+
networkingOptions: ReadableMap?,
100+
isDebuggable: Boolean
101+
): DefaultClient =
102+
networkingOptions?.let { buildNetworkingClient(it, isDebuggable) } ?: DefaultClient.Builder().build()
67103
}
68104

69105
private val errorCodeMap = mapOf(
@@ -282,19 +318,23 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
282318
useDPoP: Boolean?,
283319
maxRetries: Double,
284320
credentialsManagerStorageKey: String?,
321+
networkingOptions: ReadableMap?,
285322
promise: Promise
286323
) {
287324
// Note: maxRetries parameter is ignored on Android as the Auth0.Android SDK
288325
// does not currently support retry configuration for credential renewal.
289326
// This parameter is accepted for API compatibility with iOS.
290327

291328
this.useDPoP = useDPoP ?: false
292-
auth0 = Auth0.getInstance(clientId, domain)
293-
mfaClient = MfaClient(auth0!!, this.useDPoP, reactContext)
294-
myAccount = MyAccount(auth0!!, this.useDPoP, reactContext)
295-
passwordless = Passwordless(auth0!!, this.useDPoP, reactContext)
296-
297-
val authAPI = AuthenticationAPIClient(auth0!!)
329+
val auth0Instance = Auth0.getInstance(clientId, domain)
330+
auth0 = auth0Instance
331+
val isDebuggable = (reactContext.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
332+
auth0Instance.networkingClient = resolveNetworkingClient(networkingOptions, isDebuggable)
333+
mfaClient = MfaClient(auth0Instance, this.useDPoP, reactContext)
334+
myAccount = MyAccount(auth0Instance, this.useDPoP, reactContext)
335+
passwordless = Passwordless(auth0Instance, this.useDPoP, reactContext)
336+
337+
val authAPI = AuthenticationAPIClient(auth0Instance)
298338
if (this.useDPoP) {
299339
authAPI.useDPoP(reactContext)
300340
}

0 commit comments

Comments
 (0)