Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
887d007
Add TransportOptions for configuring TLS, proxy, and default headers
mridang Mar 4, 2026
0444f7f
Fix hostname verification for custom CA and address review feedback
mridang Mar 4, 2026
d3fcc5c
Merge custom CA with default trust store instead of replacing it
mridang Mar 4, 2026
31bccb0
Fix SSL socket resource leak in transport options test
mridang Mar 4, 2026
e7def42
Apply transport options to OAuth token exchange requests
mridang Mar 4, 2026
2f29aef
Fix custom CA cert test and apply transport options to token exchange
mridang Mar 4, 2026
c34990c
Standardize transport options tests across SDKs
mridang Mar 4, 2026
2bb9aa1
Add null guard for transportOptions in ApiClient constructor
mridang Mar 4, 2026
55beb47
Add null guards for transportOptions in all public constructors
mridang Mar 4, 2026
1dd95e3
Validate header name and value in TransportOptions builder
mridang Mar 4, 2026
16c7a55
Centralize SSL context building in TransportOptions
mridang Mar 4, 2026
8ee3d24
Verify default headers on API calls via WireMock verification
mridang Mar 4, 2026
728e978
Fix SpotBugs warning and proxy test reliability
mridang Mar 4, 2026
39765d7
Cache SSLContext in TransportOptions to avoid per-request rebuilds
mridang Mar 4, 2026
ef23bed
Fix install command from composer to Maven dependency
mridang Mar 4, 2026
ee976f4
fix: remove unnecessary SpotBugs suppression from buildSSLContext
mridang Mar 4, 2026
6677286
build: enable unused import removal in Spotless
mridang Mar 4, 2026
604bbd3
build: reorder Spotless steps to remove unused imports before formatting
mridang Mar 4, 2026
06365f3
fix: remove unused imports from ApiClient and OAuthAuthenticator
mridang Mar 4, 2026
6345a39
Add real proxy container to transport options test
mridang Mar 4, 2026
3d9cff2
chore: align docs and remove inline comments
mridang Mar 5, 2026
c9f4747
replace tinyproxy with ubuntu/squid:6.10-24.10_beta
mridang Mar 5, 2026
17bc888
catch GeneralSecurityException explicitly instead of broad Exception
mridang Mar 5, 2026
60db143
docs: fix proxy auth docs to use URL credentials instead of default h…
mridang Mar 5, 2026
bb84212
fix: replace broad catch(Exception) with specific checked exceptions
mridang Mar 5, 2026
fb43253
fix: remove stale SpotBugs filters for moved trust manager and except…
mridang Mar 5, 2026
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
74 changes: 70 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ install dependencies.

### Installation

Install the SDK by running one of the following commands:

```bash
composer require zitadel/client
Add the SDK dependency to your `pom.xml`:

```xml
<dependency>
<groupId>io.github.zitadel</groupId>
<artifactId>client</artifactId>
<version>4.1.0</version>
</dependency>
```

## Authentication Methods
Expand Down Expand Up @@ -193,6 +197,68 @@ Choose the authentication method that best suits your needs based on your
environment and security requirements. For more details, please refer to the
[Zitadel documentation on authenticating service users](https://zitadel.com/docs/guides/integrate/service-users/authenticate-service-users).

## Advanced Configuration

The SDK provides a `TransportOptions` builder that allows you to customise
the underlying HTTP transport used for both OpenID discovery and API calls.

### Disabling TLS Verification

In development or testing environments with self-signed certificates, you can
disable TLS verification entirely:

```java
TransportOptions options = new TransportOptions.Builder()
.insecure(true)
.build();

Zitadel zitadel = Zitadel.withClientCredentials(
"https://your-instance.zitadel.cloud", "client-id", "client-secret", options);
```

### Using a Custom CA Certificate

If your Zitadel instance uses a certificate signed by a private CA, you can
provide the path to the CA certificate in PEM format:

```java
TransportOptions options = new TransportOptions.Builder()
.caCertPath("/path/to/ca.pem")
.build();

Zitadel zitadel = Zitadel.withClientCredentials(
"https://your-instance.zitadel.cloud", "client-id", "client-secret", options);
```

### Custom Default Headers

You can attach default headers to every outgoing request. This is useful for
custom routing or tracing headers:

```java
TransportOptions options = new TransportOptions.Builder()
.defaultHeader("X-Custom-Header", "my-value")
.build();

Zitadel zitadel = Zitadel.withClientCredentials(
"https://your-instance.zitadel.cloud", "client-id", "client-secret", options);
```

### Proxy Configuration

If your environment requires routing traffic through an HTTP proxy, you can
specify the proxy URL. To authenticate with the proxy, embed the credentials
directly in the URL:

```java
TransportOptions options = new TransportOptions.Builder()
.proxyUrl("http://user:pass@proxy:8080")
.build();

Zitadel zitadel = Zitadel.withClientCredentials(
"https://your-instance.zitadel.cloud", "client-id", "client-secret", options);
```

## Design and Dependencies

This SDK is designed to be lean and efficient, focusing on providing a
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@
<exclude>**/api/*.java</exclude>
<exclude>**/model/*.java</exclude>
</excludes>
<removeUnusedImports/>
<googleJavaFormat/>
</java>
</configuration>
Expand Down
13 changes: 13 additions & 0 deletions spotbugs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,17 @@
<Package name="~com\.zitadel\..*"/>
<Bug pattern="THROWS_METHOD_THROWS_RUNTIMEEXCEPTION"/>
</Match>
<!-- Constructors that perform I/O intentionally throw -->
<Match>
<Or>
<Class name="com.zitadel.ApiClient"/>
<Class name="com.zitadel.auth.OpenId"/>
</Or>
<Bug pattern="CT_CONSTRUCTOR_THROW"/>
</Match>
<!-- Builder field used by subclass builders via Zitadel factory -->
<Match>
<Class name="com.zitadel.auth.OAuthAuthenticator$OAuthAuthenticatorBuilder"/>
<Bug pattern="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD"/>
</Match>
</FindBugsFilter>
56 changes: 56 additions & 0 deletions src/main/java/com/zitadel/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,27 @@
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.FileEntity;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.openapitools.jackson.nullable.JsonNullableModule;

import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.nio.charset.Charset;
Expand Down Expand Up @@ -68,7 +75,7 @@

public ApiClient(Authenticator authenticator, CloseableHttpClient httpClient) {
objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

Check warning on line 78 in src/main/java/com/zitadel/ApiClient.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Deprecated API usage

'setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include)' is deprecated
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Expand All @@ -91,6 +98,55 @@
this(authenticator, HttpClients.custom().setUserAgent(USER_AGENT).build());
}

public ApiClient(Authenticator authenticator, TransportOptions transportOptions) {
this(authenticator, buildHttpClient(transportOptions != null ? transportOptions : TransportOptions.defaults()));
}

private static CloseableHttpClient buildHttpClient(TransportOptions transportOptions) {
try {
HttpClientBuilder builder = HttpClients.custom().setUserAgent(USER_AGENT);

SSLContext sslContext = transportOptions.buildSSLContext();
if (sslContext != null) {
SSLConnectionSocketFactoryBuilder sslSocketFactoryBuilder = SSLConnectionSocketFactoryBuilder.create()

Check warning on line 111 in src/main/java/com/zitadel/ApiClient.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Deprecated API usage

'org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder' is deprecated

Check warning on line 111 in src/main/java/com/zitadel/ApiClient.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Deprecated API usage

'org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder' is deprecated
.setSslContext(sslContext);
if (transportOptions.isInsecure()) {
sslSocketFactoryBuilder.setHostnameVerifier(NoopHostnameVerifier.INSTANCE);
}
builder.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(sslSocketFactoryBuilder.build())

Check warning on line 117 in src/main/java/com/zitadel/ApiClient.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Deprecated API usage

'setSSLSocketFactory(org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory)' is deprecated
.build());
}

List<Header> defaultHeaders = new ArrayList<>();

if (transportOptions.getProxyUrl() != null) {
java.net.URL proxyParsed = new java.net.URL(transportOptions.getProxyUrl());
String proxyScheme = proxyParsed.getProtocol();
String proxyHost = proxyParsed.getHost();
int proxyPort = proxyParsed.getPort() != -1 ? proxyParsed.getPort() : proxyParsed.getDefaultPort();
builder.setProxy(new HttpHost(proxyScheme, proxyHost, proxyPort));
if (proxyParsed.getUserInfo() != null) {
String encoded = Base64.getEncoder()
.encodeToString(proxyParsed.getUserInfo().getBytes(StandardCharsets.UTF_8));
defaultHeaders.add(new BasicHeader("Proxy-Authorization", "Basic " + encoded));
}
}

for (Map.Entry<String, String> entry : transportOptions.getDefaultHeaders().entrySet()) {
defaultHeaders.add(new BasicHeader(entry.getKey(), entry.getValue()));
}

if (!defaultHeaders.isEmpty()) {
builder.setDefaultHeaders(defaultHeaders);
}

return builder.build();
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException("Failed to build HTTP client with transport options: " + e.getMessage(), e);
}
}

public static DateFormat buildDefaultDateFormat() {
return new RFC3339DateFormat();
}
Expand Down Expand Up @@ -211,7 +267,7 @@
* @param value The value of the parameter.
* @return A list containing a single {@code Pair} object.
*/
public List<Pair> parameterToPair(String name, Object value) {

Check warning on line 270 in src/main/java/com/zitadel/ApiClient.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Check Kotlin and Java source code coverage

Method `parameterToPair` coverage is below the threshold 50%
List<Pair> params = new ArrayList<>();
if (name == null || name.isEmpty() || value == null || value instanceof Collection) {
return params;
Expand Down Expand Up @@ -529,7 +585,7 @@
* @return The full URL
*/
@SuppressWarnings({"DuplicatedCode", "DuplicateExpressions"})
private String buildUrl(

Check warning on line 588 in src/main/java/com/zitadel/ApiClient.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Check Kotlin and Java source code coverage

Method `buildUrl` coverage is below the threshold 50%
String path,
List<Pair> queryParams,
@Nullable List<Pair> collectionQueryParams,
Expand Down
Loading
Loading