Skip to content

Commit eeb7fe9

Browse files
SyncServer: require new server-specific auth options for JWT auth #252
Remove SyncServerBuilder.authenticatorCredentials(SyncCredentials[]), can just call the existing builder method multiple times or use the Sync.server helper that accepts multiple credentials.
1 parent 142107e commit eeb7fe9

File tree

4 files changed

+133
-56
lines changed

4 files changed

+133
-56
lines changed

objectbox-java/src/main/java/io/objectbox/sync/Sync.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package io.objectbox.sync;
1818

19-
import javax.annotation.Nullable;
20-
2119
import io.objectbox.BoxStore;
2220
import io.objectbox.BoxStoreBuilder;
2321
import io.objectbox.sync.server.SyncServer;
@@ -85,18 +83,18 @@ public static SyncBuilder client(BoxStore boxStore, String url, SyncCredentials[
8583
* {@code ws://0.0.0.0:9999}.
8684
* @param authenticatorCredentials An authentication method available to Sync clients and peers. Additional
8785
* authenticator credentials can be supplied using the returned builder. For the embedded server, currently only
88-
* {@link SyncCredentials#sharedSecret} and {@link SyncCredentials#none} are supported. When only JWT
89-
* authentication should be possible, pass {@code null}.
86+
* {@link SyncCredentials#sharedSecret}, any JWT method like {@link SyncCredentials#jwtIdTokenServer()} as well as
87+
* {@link SyncCredentials#none} are supported.
9088
*/
91-
public static SyncServerBuilder server(BoxStore boxStore, String url, @Nullable SyncCredentials authenticatorCredentials) {
89+
public static SyncServerBuilder server(BoxStore boxStore, String url, SyncCredentials authenticatorCredentials) {
9290
return new SyncServerBuilder(boxStore, url, authenticatorCredentials);
9391
}
9492

9593
/**
9694
* Like {@link #server(BoxStore, String, SyncCredentials)}, but supports passing a set of authentication methods
9795
* for clients and peers.
9896
*/
99-
public static SyncServerBuilder server(BoxStore boxStore, String url, @Nullable SyncCredentials[] multipleAuthenticatorCredentials) {
97+
public static SyncServerBuilder server(BoxStore boxStore, String url, SyncCredentials[] multipleAuthenticatorCredentials) {
10098
return new SyncServerBuilder(boxStore, url, multipleAuthenticatorCredentials);
10199
}
102100

@@ -115,8 +113,8 @@ public static SyncServerBuilder server(BoxStore boxStore, String url, @Nullable
115113
* {@code ws://0.0.0.0:9999}.
116114
* @param authenticatorCredentials An authentication method available to Sync clients and peers. The client of the
117115
* hybrid is pre-configured with them. Additional credentials can be supplied using the client and server builder of
118-
* the returned builder. For the embedded server, currently only {@link SyncCredentials#sharedSecret} and
119-
* {@link SyncCredentials#none} are supported.
116+
* the returned builder. For the embedded server, currently only {@link SyncCredentials#sharedSecret}, any JWT
117+
* method like {@link SyncCredentials#jwtIdTokenServer()} as well as {@link SyncCredentials#none} are supported.
120118
* @return An instance of {@link SyncHybridBuilder}.
121119
*/
122120
public static SyncHybridBuilder hybrid(BoxStoreBuilder storeBuilder, String url,

objectbox-java/src/main/java/io/objectbox/sync/SyncCredentials.java

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2024 ObjectBox Ltd. All rights reserved.
2+
* Copyright 2019-2025 ObjectBox Ltd. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,47 +49,111 @@ public static SyncCredentials google(String idToken) {
4949
}
5050

5151
/**
52-
* ObjectBox admin users (username/password)
52+
* ObjectBox Admin user (username and password).
5353
*/
5454
public static SyncCredentials obxAdminUser(String user, String password) {
5555
return new SyncCredentialsUserPassword(CredentialsType.OBX_ADMIN_USER, user, password);
5656
}
5757

5858
/**
59-
* Generic credential type suitable for ObjectBox admin (and possibly others in the future)
59+
* Generic credentials type suitable for ObjectBox Admin (and possibly others in the future).
6060
*/
6161
public static SyncCredentials userAndPassword(String user, String password) {
6262
return new SyncCredentialsUserPassword(CredentialsType.USER_PASSWORD, user, password);
6363
}
6464

6565
/**
66-
* JSON Web Token (JWT): an ID token that typically provides identity information about the authenticated user.
66+
* Authenticate with a JSON Web Token (JWT) that is an ID token.
67+
* <p>
68+
* An ID token typically provides identity information about the authenticated user.
69+
* <p>
70+
* Use this and the other JWT methods that accept a token to configure JWT auth for a Sync client or server peer.
71+
* To configure Sync server auth options, use the server variants, like {@link #jwtIdTokenServer()}, instead.
72+
* <p>
73+
* See the <a href="https://sync.objectbox.io/sync-server-configuration/jwt-authentication">JWT authentication documentation</a>
74+
* for details.
6775
*/
6876
public static SyncCredentials jwtIdToken(String jwtIdToken) {
6977
return new SyncCredentialsToken(CredentialsType.JWT_ID_TOKEN, jwtIdToken);
7078
}
7179

7280
/**
73-
* JSON Web Token (JWT): an access token that is used to access resources.
81+
* Authenticate with a JSON Web Token (JWT) that is an access token.
82+
* <p>
83+
* An access token is used to access resources.
84+
* <p>
85+
* See {@link #jwtIdToken(String)} for some common remarks.
7486
*/
7587
public static SyncCredentials jwtAccessToken(String jwtAccessToken) {
7688
return new SyncCredentialsToken(CredentialsType.JWT_ACCESS_TOKEN, jwtAccessToken);
7789
}
7890

7991
/**
80-
* JSON Web Token (JWT): a refresh token that is used to obtain a new access token.
92+
* Authenticate with a JSON Web Token (JWT) that is a refresh token.
93+
* <p>
94+
* A refresh token is used to obtain a new access token.
95+
* <p>
96+
* See {@link #jwtIdToken(String)} for some common remarks.
8197
*/
8298
public static SyncCredentials jwtRefreshToken(String jwtRefreshToken) {
8399
return new SyncCredentialsToken(CredentialsType.JWT_REFRESH_TOKEN, jwtRefreshToken);
84100
}
85101

86102
/**
87-
* JSON Web Token (JWT): a token that is neither an ID, access, nor refresh token.
103+
* Authenticate with a JSON Web Token (JWT) that is neither an ID, access, nor refresh token.
104+
* <p>
105+
* See {@link #jwtIdToken(String)} for some common remarks.
88106
*/
89107
public static SyncCredentials jwtCustomToken(String jwtCustomToken) {
90108
return new SyncCredentialsToken(CredentialsType.JWT_CUSTOM_TOKEN, jwtCustomToken);
91109
}
92110

111+
/**
112+
* Enable authentication using a JSON Web Token (JWT) that is an ID token.
113+
* <p>
114+
* An ID token typically provides identity information about the authenticated user.
115+
* <p>
116+
* Use this and the other JWT server credentials types to configure a Sync server.
117+
* For Sync clients, use the ones that accept a token, like {@link #jwtIdToken(String)}, instead.
118+
* <p>
119+
* See the <a href="https://sync.objectbox.io/sync-server-configuration/jwt-authentication">JWT authentication documentation</a>
120+
* for details.
121+
*/
122+
public static SyncCredentials jwtIdTokenServer() {
123+
return new SyncCredentialsToken(CredentialsType.JWT_ID_TOKEN);
124+
}
125+
126+
/**
127+
* Enable authentication using a JSON Web Token (JWT) that is an access token.
128+
* <p>
129+
* An access token is used to access resources.
130+
* <p>
131+
* See {@link #jwtIdTokenServer()} for some common remarks.
132+
*/
133+
public static SyncCredentials jwtAccessTokenServer() {
134+
return new SyncCredentialsToken(CredentialsType.JWT_ACCESS_TOKEN);
135+
}
136+
137+
/**
138+
* Enable authentication using a JSON Web Token (JWT) that is a refresh token.
139+
* <p>
140+
* A refresh token is used to obtain a new access token.
141+
* <p>
142+
* See {@link #jwtIdTokenServer()} for some common remarks.
143+
*/
144+
public static SyncCredentials jwtRefreshTokenServer() {
145+
return new SyncCredentialsToken(CredentialsType.JWT_REFRESH_TOKEN);
146+
}
147+
148+
/**
149+
* Enable authentication using a JSON Web Token (JWT) that is neither an ID, access, nor refresh token.
150+
* <p>
151+
* See {@link #jwtIdTokenServer()} for some common remarks.
152+
*/
153+
public static SyncCredentials jwtCustomTokenServer() {
154+
return new SyncCredentialsToken(CredentialsType.JWT_CUSTOM_TOKEN);
155+
}
156+
93157
/**
94158
* No authentication, unsecured. Use only for development and testing purposes.
95159
*/

objectbox-java/src/main/java/io/objectbox/sync/SyncCredentialsToken.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2024 ObjectBox Ltd. All rights reserved.
2+
* Copyright 2019-2025 ObjectBox Ltd. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,6 +51,10 @@ public final class SyncCredentialsToken extends SyncCredentials {
5151
this(type, token.getBytes(StandardCharsets.UTF_8));
5252
}
5353

54+
public boolean hasToken() {
55+
return token != null;
56+
}
57+
5458
@Nullable
5559
public byte[] getTokenBytes() {
5660
if (cleared) {

objectbox-java/src/main/java/io/objectbox/sync/server/SyncServerBuilder.java

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,18 @@ private static void checkFeatureSyncServerAvailable() {
7272
* Use {@link Sync#server(BoxStore, String, SyncCredentials)} instead.
7373
*/
7474
@Internal
75-
public SyncServerBuilder(BoxStore boxStore, String url, @Nullable SyncCredentials authenticatorCredentials) {
75+
public SyncServerBuilder(BoxStore boxStore, String url, SyncCredentials authenticatorCredentials) {
7676
checkNotNull(boxStore, "BoxStore is required.");
7777
checkNotNull(url, "Sync server URL is required.");
78+
checkNotNull(authenticatorCredentials, "Authenticator credentials are required.");
7879
checkFeatureSyncServerAvailable();
7980
this.boxStore = boxStore;
8081
try {
8182
this.url = new URI(url);
8283
} catch (URISyntaxException e) {
8384
throw new IllegalArgumentException("Sync server URL is invalid: " + url, e);
8485
}
85-
authenticatorCredentialsOrNull(authenticatorCredentials);
86+
authenticatorCredentials(authenticatorCredentials);
8687
}
8788

8889
/**
@@ -100,7 +101,9 @@ public SyncServerBuilder(BoxStore boxStore, String url, SyncCredentials[] multip
100101
} catch (URISyntaxException e) {
101102
throw new IllegalArgumentException("Sync server URL is invalid: " + url, e);
102103
}
103-
authenticatorCredentials(multipleAuthenticatorCredentials);
104+
for (SyncCredentials credentials : multipleAuthenticatorCredentials) {
105+
authenticatorCredentials(credentials);
106+
}
104107
}
105108

106109
/**
@@ -115,48 +118,39 @@ public SyncServerBuilder certificatePath(String certificatePath) {
115118
return this;
116119
}
117120

118-
private SyncServerBuilder authenticatorCredentialsOrNull(@Nullable SyncCredentials authenticatorCredentials) {
119-
if (authenticatorCredentials == null) {
120-
return this; // Do nothing
121-
}
122-
if (!(authenticatorCredentials instanceof SyncCredentialsToken)) {
123-
throw new IllegalArgumentException("Sync credentials of type " + authenticatorCredentials.getType()
124-
+ " are not supported");
125-
}
126-
credentials.add((SyncCredentialsToken) authenticatorCredentials);
127-
return this;
128-
}
129-
130121
/**
131122
* Adds additional authenticator credentials to authenticate clients or peers with.
132123
* <p>
133-
* For the embedded server, currently only {@link SyncCredentials#sharedSecret} and {@link SyncCredentials#none}
134-
* are supported.
124+
* For the embedded server, currently only {@link SyncCredentials#sharedSecret}, any JWT method like
125+
* {@link SyncCredentials#jwtIdTokenServer()} as well as {@link SyncCredentials#none} are supported.
135126
*/
136127
public SyncServerBuilder authenticatorCredentials(SyncCredentials authenticatorCredentials) {
137128
checkNotNull(authenticatorCredentials, "Authenticator credentials must not be null.");
138-
return authenticatorCredentialsOrNull(authenticatorCredentials);
139-
}
140-
141-
/**
142-
* Adds additional authenticator credentials to authenticate clients or peers with.
143-
* <p>
144-
* For the embedded server, currently only {@link SyncCredentials#sharedSecret} and {@link SyncCredentials#none}
145-
* are supported.
146-
*/
147-
public SyncServerBuilder authenticatorCredentials(SyncCredentials[] multipleAuthenticatorCredentials) {
148-
checkNotNull(multipleAuthenticatorCredentials, "Authenticator credentials must not be null.");
149-
for (SyncCredentials credentials : multipleAuthenticatorCredentials) {
150-
authenticatorCredentials(credentials);
129+
if (!(authenticatorCredentials instanceof SyncCredentialsToken)) {
130+
throw new IllegalArgumentException("Sync credentials of type " + authenticatorCredentials.getType()
131+
+ " are not supported");
151132
}
133+
SyncCredentialsToken tokenCredential = (SyncCredentialsToken) authenticatorCredentials;
134+
SyncCredentials.CredentialsType type = tokenCredential.getType();
135+
switch (type) {
136+
case JWT_ID_TOKEN:
137+
case JWT_ACCESS_TOKEN:
138+
case JWT_REFRESH_TOKEN:
139+
case JWT_CUSTOM_TOKEN:
140+
if (tokenCredential.hasToken()) {
141+
throw new IllegalArgumentException("Must not supply a token for a credential of type "
142+
+ authenticatorCredentials.getType());
143+
}
144+
}
145+
credentials.add(tokenCredential);
152146
return this;
153147
}
154148

155149
/**
156150
* Sets a listener to observe fine granular changes happening during sync.
157151
* <p>
158-
* This listener can also be {@link SyncServer#setSyncChangeListener(SyncChangeListener) set or removed}
159-
* on the Sync server directly.
152+
* This listener can also be {@link SyncServer#setSyncChangeListener(SyncChangeListener) set or removed} on the Sync
153+
* server directly.
160154
*/
161155
public SyncServerBuilder changeListener(SyncChangeListener changeListener) {
162156
this.changeListener = changeListener;
@@ -282,6 +276,10 @@ public SyncServerBuilder workerThreads(int workerThreads) {
282276
* Sets the public key used to verify JWT tokens.
283277
* <p>
284278
* The public key should be in the PEM format.
279+
* <p>
280+
* However, typically the key is supplied using a JWKS file served from a {@link #jwtPublicKeyUrl(String)}.
281+
* <p>
282+
* See {@link #jwtPublicKeyUrl(String)} for a common configuration to enable JWT auth.
285283
*/
286284
public SyncServerBuilder jwtPublicKey(String publicKey) {
287285
this.jwtPublicKey = publicKey;
@@ -290,6 +288,19 @@ public SyncServerBuilder jwtPublicKey(String publicKey) {
290288

291289
/**
292290
* Sets the JWKS (Json Web Key Sets) URL to fetch the current public key used to verify JWT tokens.
291+
* <p>
292+
* A working JWT configuration can look like this:
293+
* <pre>{@code
294+
* SyncCredentials auth = SyncCredentials.jwtIdTokenServer();
295+
* SyncServer server = Sync.server(store, url, auth)
296+
* .jwtPublicKeyUrl("https://example.com/public-key")
297+
* .jwtClaimAud("<audience>")
298+
* .jwtClaimIss("<issuer>")
299+
* .build();
300+
* }</pre>
301+
*
302+
* See the <a href="https://sync.objectbox.io/sync-server-configuration/jwt-authentication">JWT authentication documentation</a>
303+
* for details.
293304
*/
294305
public SyncServerBuilder jwtPublicKeyUrl(String publicKeyUrl) {
295306
this.jwtPublicKeyUrl = publicKeyUrl;
@@ -298,6 +309,8 @@ public SyncServerBuilder jwtPublicKeyUrl(String publicKeyUrl) {
298309

299310
/**
300311
* Sets the JWT claim "iss" (issuer) used to verify JWT tokens.
312+
*
313+
* @see #jwtPublicKeyUrl(String)
301314
*/
302315
public SyncServerBuilder jwtClaimIss(String claimIss) {
303316
this.jwtClaimIss = claimIss;
@@ -306,6 +319,8 @@ public SyncServerBuilder jwtClaimIss(String claimIss) {
306319

307320
/**
308321
* Sets the JWT claim "aud" (audience) used to verify JWT tokens.
322+
*
323+
* @see #jwtPublicKeyUrl(String)
309324
*/
310325
public SyncServerBuilder jwtClaimAud(String claimAud) {
311326
this.jwtClaimAud = claimAud;
@@ -322,7 +337,8 @@ private boolean hasJwtConfig() {
322337
* Note: this clears all previously set authenticator credentials.
323338
*/
324339
public SyncServer build() {
325-
if (!hasJwtConfig() && credentials.isEmpty()) {
340+
// Note: even when only using JWT auth, must supply one of the credentials of JWT type
341+
if (credentials.isEmpty()) {
326342
throw new IllegalStateException("At least one authenticator is required.");
327343
}
328344
if (hasJwtConfig()) {
@@ -374,10 +390,7 @@ byte[] buildSyncServerOptions() {
374390
if (clusterId != null) {
375391
clusterIdOffset = fbb.createString(clusterId);
376392
}
377-
int authenticationMethodsOffset = 0;
378-
if (!credentials.isEmpty()) {
379-
authenticationMethodsOffset = buildAuthenticationMethods(fbb);
380-
}
393+
int authenticationMethodsOffset = buildAuthenticationMethods(fbb);
381394
int clusterPeersVectorOffset = buildClusterPeers(fbb);
382395
int jwtConfigOffset = 0;
383396
if (hasJwtConfig()) {
@@ -396,9 +409,7 @@ byte[] buildSyncServerOptions() {
396409
// After collecting all offsets, create options
397410
SyncServerOptions.startSyncServerOptions(fbb);
398411
SyncServerOptions.addUrl(fbb, urlOffset);
399-
if (authenticationMethodsOffset != 0) {
400-
SyncServerOptions.addAuthenticationMethods(fbb, authenticationMethodsOffset);
401-
}
412+
SyncServerOptions.addAuthenticationMethods(fbb, authenticationMethodsOffset);
402413
if (syncFlags != 0) {
403414
SyncServerOptions.addSyncFlags(fbb, syncFlags);
404415
}

0 commit comments

Comments
 (0)