Skip to content

Commit c02b3cc

Browse files
SoulPancakerhamzeh
andauthored
feat: oauth2 scopes for authentication (#326)
* feat: oauth2 scopes for authn * fix: readme * feat: apply suggestions for url Co-authored-by: Raghd Hamzeh <raghd@rhamzeh.com> * feat: address comments * fix: update README.md Co-authored-by: Raghd Hamzeh <raghd.hamzeh@auth0.com> --------- Co-authored-by: Raghd Hamzeh <raghd@rhamzeh.com> Co-authored-by: Raghd Hamzeh <raghd.hamzeh@auth0.com>
1 parent e379776 commit c02b3cc

3 files changed

Lines changed: 62 additions & 7 deletions

File tree

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ public class Example {
208208
.authorizationModelId(System.getenv("FGA_MODEL_ID")) // Optional, can be overridden per request
209209
.credentials(new Credentials(
210210
new ClientCredentials()
211-
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER"))
211+
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER")) // Full token endpoint URL, e.g. "https://issuer.fga.example/oauth/token"
212212
.apiAudience(System.getenv("FGA_API_AUDIENCE"))
213213
.clientId(System.getenv("FGA_CLIENT_ID"))
214214
.clientSecret(System.getenv("FGA_CLIENT_SECRET"))
@@ -220,7 +220,9 @@ public class Example {
220220
}
221221
```
222222

223-
#### Oauth2 Credentials
223+
#### OAuth2 Client Credentials
224+
225+
The SDK supports standard OAuth2 client credentials flow for any OAuth2-compliant provider (e.g. Keycloak, Okta). The `apiAudience` parameter is optional, and an optional `scopes` parameter can be provided as a space-separated string. The `apiTokenIssuer` can be set to either a hostname (e.g. `issuer.example.com`, which defaults to `https` and appends `/oauth/token`) or a full token endpoint URL (e.g. `https://mykeycloak.fga.example/realms/myrealm/protocol/openid-connect/token`).
224226

225227
```java
226228
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -238,8 +240,8 @@ public class Example {
238240
.authorizationModelId(System.getenv("FGA_MODEL_ID")) // Optional, can be overridden per request
239241
.credentials(new Credentials(
240242
new ClientCredentials()
241-
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER"))
242-
.scopes(System.getenv("FGA_API_SCOPES")) // optional space separated scopes
243+
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER")) // Full token endpoint URL, e.g. "https://mykeycloak.fga.example/realms/myrealm/protocol/openid-connect/token"
244+
.scopes(System.getenv("FGA_API_SCOPES")) // Optional, space-separated scopes
243245
.clientId(System.getenv("FGA_CLIENT_ID"))
244246
.clientSecret(System.getenv("FGA_CLIENT_SECRET"))
245247
));

src/main/java/dev/openfga/sdk/api/configuration/ClientCredentials.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static dev.openfga.sdk.util.Validation.assertParamExists;
44

55
import dev.openfga.sdk.errors.FgaInvalidParameterException;
6+
import dev.openfga.sdk.util.StringUtil;
67

78
public class ClientCredentials {
89
private String clientId;
@@ -22,7 +23,6 @@ public void assertValid() throws FgaInvalidParameterException {
2223
assertParamExists(clientId, "clientId", "ClientCredentials");
2324
assertParamExists(clientSecret, "clientSecret", "ClientCredentials");
2425
assertParamExists(apiTokenIssuer, "apiTokenIssuer", "ClientCredentials");
25-
assertParamExists(apiAudience, "apiAudience", "ClientCredentials");
2626
}
2727

2828
public String getClientId() {
@@ -48,7 +48,7 @@ public String getApiTokenIssuer() {
4848
}
4949

5050
public ClientCredentials apiAudience(String apiAudience) {
51-
this.apiAudience = apiAudience;
51+
this.apiAudience = StringUtil.isNullOrWhitespace(apiAudience) ? null : apiAudience;
5252
return this;
5353
}
5454

@@ -57,7 +57,7 @@ public String getApiAudience() {
5757
}
5858

5959
public ClientCredentials scopes(String scopes) {
60-
this.scopes = scopes;
60+
this.scopes = StringUtil.isNullOrWhitespace(scopes) ? null : scopes;
6161
return this;
6262
}
6363

src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,57 @@ public void assertValid_invalidApiTokenIssuer() {
8080
"Required parameter apiTokenIssuer was invalid when calling ClientCredentials.",
8181
exception.getMessage()));
8282
}
83+
84+
@Test
85+
public void assertValid_withoutApiAudience() throws FgaInvalidParameterException {
86+
// audience is optional for standard OAuth2 servers
87+
ClientCredentials creds = new ClientCredentials()
88+
.clientId(VALID_CLIENT_ID)
89+
.clientSecret(VALID_CLIENT_SECRET)
90+
.apiTokenIssuer(VALID_API_TOKEN_ISSUER);
91+
92+
// Should not throw
93+
creds.assertValid();
94+
assertNull(creds.getApiAudience());
95+
}
96+
97+
@Test
98+
public void assertValid_withScopes() throws FgaInvalidParameterException {
99+
ClientCredentials creds = new ClientCredentials()
100+
.clientId(VALID_CLIENT_ID)
101+
.clientSecret(VALID_CLIENT_SECRET)
102+
.apiTokenIssuer(VALID_API_TOKEN_ISSUER)
103+
.scopes("read write");
104+
105+
creds.assertValid();
106+
assertEquals("read write", creds.getScopes());
107+
}
108+
109+
@Test
110+
public void assertValid_blankApiAudienceNormalizedToNull() throws FgaInvalidParameterException {
111+
for (String blank : Arrays.asList("", " ", "\t\r\n")) {
112+
ClientCredentials creds = new ClientCredentials()
113+
.clientId(VALID_CLIENT_ID)
114+
.clientSecret(VALID_CLIENT_SECRET)
115+
.apiTokenIssuer(VALID_API_TOKEN_ISSUER)
116+
.apiAudience(blank);
117+
118+
creds.assertValid();
119+
assertNull(creds.getApiAudience(), "Blank apiAudience should be normalized to null");
120+
}
121+
}
122+
123+
@Test
124+
public void assertValid_blankScopesNormalizedToNull() throws FgaInvalidParameterException {
125+
for (String blank : Arrays.asList("", " ", "\t\r\n")) {
126+
ClientCredentials creds = new ClientCredentials()
127+
.clientId(VALID_CLIENT_ID)
128+
.clientSecret(VALID_CLIENT_SECRET)
129+
.apiTokenIssuer(VALID_API_TOKEN_ISSUER)
130+
.scopes(blank);
131+
132+
creds.assertValid();
133+
assertNull(creds.getScopes(), "Blank scopes should be normalized to null");
134+
}
135+
}
83136
}

0 commit comments

Comments
 (0)