diff --git a/src/main/java/com/auth0/AuthenticationController.java b/src/main/java/com/auth0/AuthenticationController.java index c768765..62cee5a 100644 --- a/src/main/java/com/auth0/AuthenticationController.java +++ b/src/main/java/com/auth0/AuthenticationController.java @@ -1,6 +1,7 @@ package com.auth0; import com.auth0.jwk.JwkProvider; +import com.auth0.net.client.Auth0HttpClient; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.Validate; @@ -73,6 +74,7 @@ public static class Builder { private final String clientSecret; private String responseType; private JwkProvider jwkProvider; + private Auth0HttpClient httpClient; private Integer clockSkew; private Integer authenticationMaxAge; private boolean useLegacySameSiteCookie; @@ -179,6 +181,39 @@ public Builder withJwkProvider(JwkProvider jwkProvider) { return this; } + /** + * Sets a custom {@link Auth0HttpClient} to use for all HTTP requests made by this library + * (token exchange, PAR, etc.). Use this to configure timeouts, proxies, or other HTTP settings. + * + *
Note: When a custom {@code Auth0HttpClient} is provided, the + * {@link AuthenticationController#setLoggingEnabled(boolean)} and + * {@link AuthenticationController#doNotSendTelemetry()} settings will have no effect, + * as those are configured at the HTTP client level. You should configure logging and + * telemetry directly on the client instance before passing it here.
+ * + *{@code
+ * Auth0HttpClient httpClient = DefaultHttpClient.newBuilder()
+ * .withConnectTimeout(10)
+ * .withReadTimeout(10)
+ * .telemetryEnabled(false)
+ * .withLogging(new LoggingOptions(LoggingOptions.LogLevel.BODY))
+ * .build();
+ *
+ * AuthenticationController controller = AuthenticationController
+ * .newBuilder(domain, clientId, clientSecret)
+ * .withHttpClient(httpClient)
+ * .build();
+ * }
+ *
+ * @param httpClient a configured {@link Auth0HttpClient} instance.
+ * @return this same builder instance.
+ */
+ public Builder withHttpClient(Auth0HttpClient httpClient) {
+ Validate.notNull(httpClient, "httpClient must not be null");
+ this.httpClient = httpClient;
+ return this;
+ }
+
/**
* Sets the clock-skew or leeway value to use in the ID Token verification. The value must be in seconds.
* Defaults to 60 seconds.
@@ -267,6 +302,9 @@ public AuthenticationController build() throws UnsupportedOperationException {
if (jwkProvider != null) {
builder.withJwkProvider(jwkProvider);
}
+ if (httpClient != null) {
+ builder.withHttpClient(httpClient);
+ }
return new AuthenticationController(builder.build());
}
diff --git a/src/main/java/com/auth0/RequestProcessor.java b/src/main/java/com/auth0/RequestProcessor.java
index 36a53cc..c1e9512 100644
--- a/src/main/java/com/auth0/RequestProcessor.java
+++ b/src/main/java/com/auth0/RequestProcessor.java
@@ -11,6 +11,7 @@
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
+import com.auth0.net.client.Auth0HttpClient;
import com.auth0.net.client.DefaultHttpClient;
import com.auth0.utils.tokens.IdTokenVerifier;
import com.auth0.utils.tokens.SignatureVerifier;
@@ -50,6 +51,7 @@ class RequestProcessor {
private final String clientId;
private final String clientSecret;
private final JwkProvider jwkProvider;
+ private Auth0HttpClient httpClient;
private final Integer clockSkew;
private final Integer authenticationMaxAge;
@@ -71,6 +73,7 @@ static class Builder {
private final String clientSecret;
private JwkProvider jwkProvider;
+ private Auth0HttpClient httpClient;
private boolean useLegacySameSiteCookie = true;
private Integer clockSkew;
private Integer authenticationMaxAge;
@@ -93,6 +96,11 @@ Builder withJwkProvider(JwkProvider jwkProvider) {
return this;
}
+ Builder withHttpClient(Auth0HttpClient httpClient) {
+ this.httpClient = httpClient;
+ return this;
+ }
+
public Builder withClockSkew(Integer clockSkew) {
this.clockSkew = clockSkew;
return this;
@@ -125,13 +133,13 @@ Builder withInvitation(String invitation) {
RequestProcessor build() {
return new RequestProcessor(domainProvider, responseType, clientId, clientSecret,
- jwkProvider, useLegacySameSiteCookie, clockSkew, authenticationMaxAge,
+ jwkProvider, httpClient, useLegacySameSiteCookie, clockSkew, authenticationMaxAge,
organization, invitation, cookiePath);
}
}
private RequestProcessor(DomainProvider domainProvider, String responseType, String clientId,
- String clientSecret, JwkProvider jwkProvider,
+ String clientSecret, JwkProvider jwkProvider, Auth0HttpClient httpClient,
boolean useLegacySameSiteCookie, Integer clockSkew, Integer authenticationMaxAge,
String organization, String invitation, String cookiePath) {
this.domainProvider = domainProvider;
@@ -139,6 +147,7 @@ private RequestProcessor(DomainProvider domainProvider, String responseType, Str
this.clientId = clientId;
this.clientSecret = clientSecret;
this.jwkProvider = jwkProvider;
+ this.httpClient = httpClient;
this.useLegacySameSiteCookie = useLegacySameSiteCookie;
this.clockSkew = clockSkew;
this.authenticationMaxAge = authenticationMaxAge;
@@ -156,18 +165,23 @@ void doNotSendTelemetry() {
}
AuthAPI createClientForDomain(String domain) {
- DefaultHttpClient.Builder httpBuilder = DefaultHttpClient.newBuilder()
- .telemetryEnabled(!telemetryDisabled);
-
- if (loggingEnabled) {
- httpBuilder.withLogging(new LoggingOptions(LoggingOptions.LogLevel.BODY));
- }
-
return AuthAPI.newBuilder(domain, clientId, clientSecret)
- .withHttpClient(httpBuilder.build())
+ .withHttpClient(getHttpClient())
.build();
}
+ private Auth0HttpClient getHttpClient() {
+ if (this.httpClient == null) {
+ DefaultHttpClient.Builder httpBuilder = DefaultHttpClient.newBuilder()
+ .telemetryEnabled(!telemetryDisabled);
+ if (loggingEnabled) {
+ httpBuilder.withLogging(new LoggingOptions(LoggingOptions.LogLevel.BODY));
+ }
+ this.httpClient = httpBuilder.build();
+ }
+ return this.httpClient;
+ }
+
/**
* Pre builds an Auth0 Authorize Url with the given redirect URI, state and nonce parameters.
*
diff --git a/src/test/java/com/auth0/AuthenticationControllerTest.java b/src/test/java/com/auth0/AuthenticationControllerTest.java
index 4fc78ea..6af69ac 100644
--- a/src/test/java/com/auth0/AuthenticationControllerTest.java
+++ b/src/test/java/com/auth0/AuthenticationControllerTest.java
@@ -1,6 +1,7 @@
package com.auth0;
import com.auth0.jwk.JwkProvider;
+import com.auth0.net.client.Auth0HttpClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
@@ -31,6 +32,8 @@ public class AuthenticationControllerTest {
@Mock
private DomainResolver mockDomainResolver;
@Mock
+ private Auth0HttpClient mockHttpClient;
+ @Mock
private Tokens mockTokens;
private HttpServletRequest request;
@@ -82,6 +85,7 @@ public void shouldConfigureBuilderWithAllOptions() {
AuthenticationController controller = AuthenticationController.newBuilder(DOMAIN, CLIENT_ID, CLIENT_SECRET)
.withResponseType("id_token token")
.withJwkProvider(mockJwkProvider)
+ .withHttpClient(mockHttpClient)
.withClockSkew(120)
.withAuthenticationMaxAge(3600)
.withLegacySameSiteCookie(false)
@@ -131,6 +135,7 @@ public void shouldValidateNullParameters() {
assertThrows(NullPointerException.class, () -> builder.withDomain(null));
assertThrows(NullPointerException.class, () -> builder.withResponseType(null));
assertThrows(NullPointerException.class, () -> builder.withJwkProvider(null));
+ assertThrows(NullPointerException.class, () -> builder.withHttpClient(null));
assertThrows(NullPointerException.class, () -> builder.withClockSkew(null));
assertThrows(NullPointerException.class, () -> builder.withAuthenticationMaxAge(null));
assertThrows(NullPointerException.class, () -> builder.withOrganization(null));
@@ -421,4 +426,43 @@ public void shouldHandleImplicitGrantResponseType() {
assertThat(controller, is(notNullValue()));
}
+
+ // --- HttpClient Configuration Tests ---
+
+ @Test
+ public void shouldBuildWithCustomHttpClient() {
+ AuthenticationController controller = AuthenticationController.newBuilder(DOMAIN, CLIENT_ID, CLIENT_SECRET)
+ .withHttpClient(mockHttpClient)
+ .build();
+
+ assertThat(controller, is(notNullValue()));
+ assertThat(controller.getRequestProcessor(), is(notNullValue()));
+ }
+
+ @Test
+ public void shouldBuildWithCustomHttpClientAndJwkProvider() {
+ AuthenticationController controller = AuthenticationController.newBuilder(DOMAIN, CLIENT_ID, CLIENT_SECRET)
+ .withHttpClient(mockHttpClient)
+ .withJwkProvider(mockJwkProvider)
+ .build();
+
+ assertThat(controller, is(notNullValue()));
+ }
+
+ @Test
+ public void shouldBuildWithCustomHttpClientAndDomainResolver() {
+ AuthenticationController controller = AuthenticationController
+ .newBuilder(mockDomainResolver, CLIENT_ID, CLIENT_SECRET)
+ .withHttpClient(mockHttpClient)
+ .build();
+
+ assertThat(controller, is(notNullValue()));
+ }
+
+ @Test
+ public void shouldThrowExceptionWhenHttpClientIsNull() {
+ AuthenticationController.Builder builder = AuthenticationController.newBuilder(DOMAIN, CLIENT_ID, CLIENT_SECRET);
+
+ assertThrows(NullPointerException.class, () -> builder.withHttpClient(null));
+ }
}
diff --git a/src/test/java/com/auth0/RequestProcessorTest.java b/src/test/java/com/auth0/RequestProcessorTest.java
index ec30c56..0851418 100644
--- a/src/test/java/com/auth0/RequestProcessorTest.java
+++ b/src/test/java/com/auth0/RequestProcessorTest.java
@@ -6,6 +6,7 @@
import com.auth0.jwk.JwkProvider;
import com.auth0.net.Response;
import com.auth0.net.TokenRequest;
+import com.auth0.net.client.Auth0HttpClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
@@ -44,6 +45,8 @@ public class RequestProcessorTest {
@Mock
private JwkProvider mockJwkProvider;
@Mock
+ private Auth0HttpClient mockHttpClient;
+ @Mock
private AuthAPI mockAuthAPI;
@Mock
private TokenRequest mockTokenRequest;
@@ -646,6 +649,264 @@ public void shouldSupportAuthenticationMaxAge() {
assertThat(processor, is(notNullValue()));
}
+ // --- Custom HttpClient Tests ---
+
+ @Test
+ public void shouldBuildWithCustomHttpClient() {
+ RequestProcessor processor = new RequestProcessor.Builder(
+ mockDomainProvider,
+ RESPONSE_TYPE_CODE,
+ CLIENT_ID,
+ CLIENT_SECRET)
+ .withHttpClient(mockHttpClient)
+ .build();
+
+ assertThat(processor, is(notNullValue()));
+ }
+
+ @Test
+ public void shouldCreateClientForDomainWithCustomHttpClient() {
+ RequestProcessor processor = new RequestProcessor.Builder(
+ mockDomainProvider,
+ RESPONSE_TYPE_CODE,
+ CLIENT_ID,
+ CLIENT_SECRET)
+ .withHttpClient(mockHttpClient)
+ .build();
+
+ AuthAPI client = processor.createClientForDomain(DOMAIN);
+ assertThat(client, is(notNullValue()));
+ }
+
+ @Test
+ public void shouldReuseCustomHttpClientAcrossMultipleDomains() {
+ RequestProcessor processor = new RequestProcessor.Builder(
+ mockDomainProvider,
+ RESPONSE_TYPE_CODE,
+ CLIENT_ID,
+ CLIENT_SECRET)
+ .withHttpClient(mockHttpClient)
+ .build();
+
+ AuthAPI client1 = processor.createClientForDomain("domain1.auth0.com");
+ AuthAPI client2 = processor.createClientForDomain("domain2.auth0.com");
+
+ assertThat(client1, is(notNullValue()));
+ assertThat(client2, is(notNullValue()));
+ }
+
+ @Test
+ public void shouldCreateDefaultHttpClientWhenNoneProvided() {
+ RequestProcessor processor = new RequestProcessor.Builder(
+ mockDomainProvider,
+ RESPONSE_TYPE_CODE,
+ CLIENT_ID,
+ CLIENT_SECRET)
+ .build();
+
+ AuthAPI client = processor.createClientForDomain(DOMAIN);
+ assertThat(client, is(notNullValue()));
+ }
+
+ @Test
+ public void shouldReuseDefaultHttpClientAcrossMultipleCalls() {
+ RequestProcessor processor = new RequestProcessor.Builder(
+ mockDomainProvider,
+ RESPONSE_TYPE_CODE,
+ CLIENT_ID,
+ CLIENT_SECRET)
+ .build();
+
+ AuthAPI client1 = processor.createClientForDomain("domain1.auth0.com");
+ AuthAPI client2 = processor.createClientForDomain("domain2.auth0.com");
+
+ assertThat(client1, is(notNullValue()));
+ assertThat(client2, is(notNullValue()));
+ }
+
+ @Test
+ public void shouldBuildWithHttpClientAndJwkProvider() {
+ RequestProcessor processor = new RequestProcessor.Builder(
+ mockDomainProvider,
+ RESPONSE_TYPE_CODE,
+ CLIENT_ID,
+ CLIENT_SECRET)
+ .withHttpClient(mockHttpClient)
+ .withJwkProvider(mockJwkProvider)
+ .build();
+
+ assertThat(processor, is(notNullValue()));
+ }
+
+ // --- Transaction-Keyed Cookie Tests ---
+
+ @Test
+ public void shouldValidateStateFromTransactionKeyedCookie() {
+ when(mockDomainProvider.getDomain(any())).thenReturn(DOMAIN);
+
+ Map