Repository navigation
Description
Checklist
- The issue can be reproduced in the Auth0.Android sample app (or N/A).
- I have looked into the Readme, Examples, and FAQ and have not found a suitable solution or answer.
- I have looked into the API documentation and have not found a suitable solution or answer.
- I have searched the issues and have not found a suitable solution or answer.
- I have searched the Auth0 Community forums and have not found a suitable solution or answer.
- I agree to the terms within the Auth0 Code of Conduct.
Description
Hi! I'm a React Native developer. Our app signs in with react-native-auth0 5.11, which uses this SDK on Android and turns DPoP on by default. Thanks for maintaining it.
When DPoP is on, requests with a DPoP header, such as the code exchange after the browser login, go through an OkHttp client with retryOnConnectionFailure(false). In OkHttp 4, that setting also stops OkHttp from trying the host's next IP address when the first one doesn't answer.
On a phone with both IPv6 and IPv4, Android puts the IPv6 address first. If the IPv6 route is broken, Chrome starts an IPv4 attempt after 300 ms, so the login page works. Then the code exchange waits out the 10 s connect timeout on the IPv6 address and fails with a network error (a0.network_error in react-native-auth0).
This matches what we see in production: Android logins fail at the code exchange far more often when the browser part of the login went over IPv4 (13–79 % on Android vs under 1 % on iOS, depending on the network). When it went over IPv6, both are at about 1 %.
Requests without DPoP move on to the next address and succeed, after the same 10 s wait. I expected DPoP requests to do the same. Nothing has been sent when the connection fails, so no proof gets used twice.
#1125 doesn't cover this case: here the error is a SocketTimeoutException, and a new call starts at the same first address again (call 2 below).
Reproduction
A plain JVM program with OkHttp 4.12.0 (the version this SDK uses) and the SDK's default 10 s timeouts. The DNS answer puts an address that never answers in front of example.com's real addresses. To OkHttp, that looks like a broken IPv6 route.
Repro.java
import okhttp3.*;
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
// Auth0.Android sends DPoP requests through okHttpClient.newBuilder().retryOnConnectionFailure(false).
// Here the host's first address never answers, like a broken IPv6 route. The next ones work.
public class Repro {
public static void main(String[] args) throws Exception {
InetAddress dead = InetAddress.getByName("10.255.255.1"); // no route, so connect() times out
Dns dns = host -> {
List<InetAddress> addresses = new ArrayList<>(List.of(dead));
addresses.addAll(Dns.SYSTEM.lookup(host));
return addresses;
};
// Each client is built on its own, so they share no connection pool.
OkHttpClient dpopClient = client(dns).retryOnConnectionFailure(false).build();
run("retryOnConnectionFailure(false), call 1", dpopClient);
run("retryOnConnectionFailure(false), call 2", dpopClient);
run("OkHttp default, as for requests without DPoP", client(dns).build());
}
static OkHttpClient.Builder client(Dns dns) {
return new OkHttpClient.Builder()
.dns(dns)
.connectTimeout(10, TimeUnit.SECONDS) // DefaultClient's default timeouts
.readTimeout(10, TimeUnit.SECONDS)
.eventListener(new EventListener() {
@Override public void connectStart(Call call, InetSocketAddress address, Proxy proxy) {
System.out.println(" connecting to " + address.getAddress().getHostAddress());
}
@Override public void connectFailed(Call call, InetSocketAddress address, Proxy proxy,
Protocol protocol, IOException e) {
System.out.println(" failed: " + e);
}
});
}
static void run(String name, OkHttpClient client) {
System.out.println(name);
long start = System.nanoTime();
Request request = new Request.Builder().url("https://example.com/").build();
try (Response response = client.newCall(request).execute()) {
System.out.printf(" HTTP %d after %.1f s%n", response.code(), (System.nanoTime() - start) / 1e9);
} catch (IOException e) {
System.out.printf(" %s after %.1f s%n", e, (System.nanoTime() - start) / 1e9);
}
}
}Run it with three jars from Maven Central (com.squareup.okhttp3:okhttp:4.12.0, com.squareup.okio:okio-jvm:3.6.0, org.jetbrains.kotlin:kotlin-stdlib:1.8.21). I used Java 17 on macOS:
java -cp okhttp-4.12.0.jar:okio-jvm-3.6.0.jar:kotlin-stdlib-1.8.21.jar Repro.java
retryOnConnectionFailure(false), call 1
connecting to 10.255.255.1
failed: java.net.SocketTimeoutException: Connect timed out
java.net.SocketTimeoutException: Connect timed out after 10.0 s
retryOnConnectionFailure(false), call 2
connecting to 10.255.255.1
failed: java.net.SocketTimeoutException: Connect timed out
java.net.SocketTimeoutException: Connect timed out after 10.0 s
OkHttp default, as for requests without DPoP
connecting to 10.255.255.1
failed: java.net.SocketTimeoutException: Connect timed out
connecting to 172.66.147.243
HTTP 200 after 10.3 s
It fails on every run. 10.255.255.1 has to drop the connection attempt: behind a VPN that answers every address, the output shows a TLS Read timed out instead, with the same result. I didn't use the sample app, because it needs a network whose first address doesn't answer.
Additional context
Two ideas, in case they help:
- Let DPoP requests move on to the next address when the connection fails before anything is sent.
- Or let apps set the address order, for example with a
dns()option onDefaultClient.Builder.
The first one still costs the 10 s wait, but the login goes through. Today the only way around it is an app's own NetworkingClient, and that one can't reuse the SDK's DPoP nonce retry, because it isn't public. We're testing that now, with IPv4 first. I haven't tested either idea on a device. Happy to test a build.
Auth0.Android version
3.21.x (through react-native-auth0 5.11). The no-retry DPoP client is in every version from 3.13.0 up to 4.3.0, 5.0.0-beta.2 and main; 3.12.2 doesn't have it.
Android version(s)
Not tied to an Android version: this is OkHttp behaviour. The repro runs on the JVM.
Activity
pmathew92 commentedon Nov 6, 2026
Hi @jeltehomminga , Thanks for raising this issue with us. We will take a look at this
cc @utkrishtsahu