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
24 changes: 24 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,27 @@ jobs:
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f
with:
directory: coverage

android-test:
name: Run Android tests
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

- name: Setup
uses: ./.github/actions/setup

- name: Set up JDK 17
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5

- name: Run Android unit tests
working-directory: example/android
run: ./gradlew :react-native-auth0:testDebugUnitTest
63 changes: 63 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
- [Using Retry with Auth0 Class](#using-retry-with-auth0-class)
- [Platform Support](#platform-support)
- [Error Handling](#error-handling)
- [Android Networking Configuration](#android-networking-configuration)
- [Using Networking Options with Hooks](#using-networking-options-with-hooks)
- [Using Networking Options with Auth0 Class](#using-networking-options-with-auth0-class)
- [IPSIE Session Expiry](#ipsie-session-expiry)
- [Biometric Authentication](#biometric-authentication)
- [Biometric Policy Types](#biometric-policy-types)
Expand Down Expand Up @@ -637,6 +640,66 @@ function MyComponent() {
2. **Configure adequate overlap period**: Ensure your Auth0 tenant has at least 180 seconds token overlap configured
3. **Test on real devices**: Simulate network instability during testing to validate retry behavior

## Android Networking Configuration

> **Platform Support:** Android only. Accepted on iOS for API compatibility but has no effect.

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.

```ts
networkingOptions?: {
connectTimeout?: number; // seconds, default 10
readTimeout?: number; // seconds, default 10
writeTimeout?: number; // seconds, default 10
callTimeout?: number; // seconds, default 0 (no limit)
defaultHeaders?: Record<string, string>; // sent on every request, default {}
enableLogging?: boolean; // default false
};
```

Any option you omit falls back to Auth0.Android's own default.

> [!WARNING]
> `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.

### Using Networking Options with Hooks

```jsx
import React from 'react';
import { Auth0Provider } from 'react-native-auth0';

function App() {
return (
<Auth0Provider
domain="YOUR_AUTH0_DOMAIN"
clientId="YOUR_AUTH0_CLIENT_ID"
networkingOptions={{
connectTimeout: 30,
readTimeout: 30,
defaultHeaders: { 'X-App-Version': '1.2.3' },
}}
>
<MyComponent />
</Auth0Provider>
);
}
```

### Using Networking Options with Auth0 Class

```js
import Auth0 from 'react-native-auth0';

const auth0 = new Auth0({
domain: 'YOUR_AUTH0_DOMAIN',
clientId: 'YOUR_AUTH0_CLIENT_ID',
networkingOptions: {
connectTimeout: 30,
readTimeout: 30,
},
});
```

## IPSIE Session Expiry

> **Platform Support:** iOS, Android, and Web.
Expand Down
3 changes: 3 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.browser:browser:1.10.0"
implementation 'com.auth0.android:auth0:4.0.1'

testImplementation 'junit:junit:4.13.2'
Comment thread
NandanPrabhu marked this conversation as resolved.
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
}

react {
Expand Down
52 changes: 46 additions & 6 deletions android/src/main/java/com/auth0/react/A0Auth0Module.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.auth0.react

import android.app.Activity
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.os.Build
import android.os.Handler
import android.os.Looper
Expand All @@ -20,6 +21,7 @@ import com.auth0.android.dpop.DPoPException
import com.auth0.android.provider.BrowserPicker
import com.auth0.android.provider.CustomTabsOptions
import com.auth0.android.provider.WebAuthProvider
import com.auth0.android.request.DefaultClient
import com.auth0.android.request.PublicKeyCredentials
import com.auth0.android.request.UserData
import com.auth0.android.result.APICredentials
Expand Down Expand Up @@ -64,6 +66,40 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
private const val DPOP_INVALID_TOKEN_TYPE_CODE = "DPOP_INVALID_TOKEN_TYPE"
private const val DPOP_MISSING_PARAMETER_CODE = "DPOP_MISSING_PARAMETER"
private const val DPOP_CLEAR_KEY_FAILED_CODE = "DPOP_CLEAR_KEY_FAILED"

// Builds the DefaultClient Auth0.Android uses for every request it makes (web auth
// token exchange, credential renewal, MFA, passkeys, etc.). Unset keys fall through to
// Auth0.Android's own Builder defaults. `enableLogging` is debug-only: Auth0.Android logs
// full request/response bodies (including tokens) at that level, so we never call
// `logger(...)` ourselves, never expose the raw HttpLoggingInterceptor.Logger to JS, and
// ignore the option entirely unless the host app is a debug build.
internal fun buildNetworkingClient(options: ReadableMap, isDebuggable: Boolean): DefaultClient {
val builder = DefaultClient.Builder()
if (options.hasKey("connectTimeout")) builder.connectTimeout(options.getInt("connectTimeout"))
if (options.hasKey("readTimeout")) builder.readTimeout(options.getInt("readTimeout"))
if (options.hasKey("writeTimeout")) builder.writeTimeout(options.getInt("writeTimeout"))
if (options.hasKey("callTimeout")) builder.callTimeout(options.getInt("callTimeout"))
options.getMap("defaultHeaders")?.let { headers ->
builder.defaultHeaders(headers.toHashMap().mapValues { it.value?.toString() ?: "" })
}
// Only honor enableLogging on debug builds: Auth0.Android logs full request/response
// bodies at this level, including plaintext access/refresh/ID tokens from token-endpoint
// responses. Test coverage in A0Auth0ModuleNetworkingOptionsTest ensures this gate holds.
if (isDebuggable && options.hasKey("enableLogging")) {
builder.enableLogging(options.getBoolean("enableLogging"))
}
return builder.build()
}

// Auth0.getInstance() returns a shared singleton per clientId/domain: a sibling client
// (or this same client on re-init) must not inherit another initialization's networking
// config, so this always resolves to a fresh DefaultClient() when options are absent
// rather than leaving the previous networkingClient in place.
internal fun resolveNetworkingClient(
networkingOptions: ReadableMap?,
isDebuggable: Boolean
): DefaultClient =
networkingOptions?.let { buildNetworkingClient(it, isDebuggable) } ?: DefaultClient.Builder().build()
}

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

this.useDPoP = useDPoP ?: false
auth0 = Auth0.getInstance(clientId, domain)
mfaClient = MfaClient(auth0!!, this.useDPoP, reactContext)
myAccount = MyAccount(auth0!!, this.useDPoP, reactContext)
passwordless = Passwordless(auth0!!, this.useDPoP, reactContext)

val authAPI = AuthenticationAPIClient(auth0!!)
val auth0Instance = Auth0.getInstance(clientId, domain)
auth0 = auth0Instance
val isDebuggable = (reactContext.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
auth0Instance.networkingClient = resolveNetworkingClient(networkingOptions, isDebuggable)
mfaClient = MfaClient(auth0Instance, this.useDPoP, reactContext)
myAccount = MyAccount(auth0Instance, this.useDPoP, reactContext)
passwordless = Passwordless(auth0Instance, this.useDPoP, reactContext)

val authAPI = AuthenticationAPIClient(auth0Instance)
if (this.useDPoP) {
authAPI.useDPoP(reactContext)
}
Expand Down
Loading
Loading