Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public class Example {
.authorizationModelId(System.getenv("FGA_MODEL_ID")) // Optional, can be overridden per request
.credentials(new Credentials(
new ClientCredentials()
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER"))
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER")) // Full token endpoint URL, e.g. "https://issuer.fga.example/oauth/token"
.apiAudience(System.getenv("FGA_API_AUDIENCE"))
.clientId(System.getenv("FGA_CLIENT_ID"))
.clientSecret(System.getenv("FGA_CLIENT_SECRET"))
Expand All @@ -220,7 +220,9 @@ public class Example {
}
```

#### Oauth2 Credentials
#### OAuth2 Client Credentials

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`).

```java
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -238,8 +240,8 @@ public class Example {
.authorizationModelId(System.getenv("FGA_MODEL_ID")) // Optional, can be overridden per request
.credentials(new Credentials(
new ClientCredentials()
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER"))
.scopes(System.getenv("FGA_API_SCOPES")) // optional space separated scopes
.apiTokenIssuer(System.getenv("FGA_API_TOKEN_ISSUER")) // Full token endpoint URL, e.g. "https://mykeycloak.fga.example/realms/myrealm/protocol/openid-connect/token"
.scopes(System.getenv("FGA_API_SCOPES")) // Optional, space-separated scopes
.clientId(System.getenv("FGA_CLIENT_ID"))
Comment thread
SoulPancake marked this conversation as resolved.
.clientSecret(System.getenv("FGA_CLIENT_SECRET"))
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static dev.openfga.sdk.util.Validation.assertParamExists;

import dev.openfga.sdk.errors.FgaInvalidParameterException;
import dev.openfga.sdk.util.StringUtil;

public class ClientCredentials {
private String clientId;
Expand All @@ -22,7 +23,6 @@ public void assertValid() throws FgaInvalidParameterException {
assertParamExists(clientId, "clientId", "ClientCredentials");
assertParamExists(clientSecret, "clientSecret", "ClientCredentials");
assertParamExists(apiTokenIssuer, "apiTokenIssuer", "ClientCredentials");
assertParamExists(apiAudience, "apiAudience", "ClientCredentials");
}

public String getClientId() {
Expand All @@ -48,7 +48,7 @@ public String getApiTokenIssuer() {
}

public ClientCredentials apiAudience(String apiAudience) {
this.apiAudience = apiAudience;
this.apiAudience = StringUtil.isNullOrWhitespace(apiAudience) ? null : apiAudience;
return this;
}

Expand All @@ -57,7 +57,7 @@ public String getApiAudience() {
}

public ClientCredentials scopes(String scopes) {
this.scopes = scopes;
this.scopes = StringUtil.isNullOrWhitespace(scopes) ? null : scopes;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,57 @@ public void assertValid_invalidApiTokenIssuer() {
"Required parameter apiTokenIssuer was invalid when calling ClientCredentials.",
exception.getMessage()));
}

@Test
public void assertValid_withoutApiAudience() throws FgaInvalidParameterException {
// audience is optional for standard OAuth2 servers
ClientCredentials creds = new ClientCredentials()
.clientId(VALID_CLIENT_ID)
.clientSecret(VALID_CLIENT_SECRET)
.apiTokenIssuer(VALID_API_TOKEN_ISSUER);

// Should not throw
creds.assertValid();
assertNull(creds.getApiAudience());
}

@Test
public void assertValid_withScopes() throws FgaInvalidParameterException {
ClientCredentials creds = new ClientCredentials()
.clientId(VALID_CLIENT_ID)
.clientSecret(VALID_CLIENT_SECRET)
.apiTokenIssuer(VALID_API_TOKEN_ISSUER)
.scopes("read write");

creds.assertValid();
assertEquals("read write", creds.getScopes());
}
Comment thread
SoulPancake marked this conversation as resolved.

@Test
public void assertValid_blankApiAudienceNormalizedToNull() throws FgaInvalidParameterException {
for (String blank : Arrays.asList("", " ", "\t\r\n")) {
ClientCredentials creds = new ClientCredentials()
.clientId(VALID_CLIENT_ID)
.clientSecret(VALID_CLIENT_SECRET)
.apiTokenIssuer(VALID_API_TOKEN_ISSUER)
.apiAudience(blank);

creds.assertValid();
assertNull(creds.getApiAudience(), "Blank apiAudience should be normalized to null");
}
}

@Test
public void assertValid_blankScopesNormalizedToNull() throws FgaInvalidParameterException {
for (String blank : Arrays.asList("", " ", "\t\r\n")) {
ClientCredentials creds = new ClientCredentials()
.clientId(VALID_CLIENT_ID)
.clientSecret(VALID_CLIENT_SECRET)
.apiTokenIssuer(VALID_API_TOKEN_ISSUER)
.scopes(blank);

creds.assertValid();
assertNull(creds.getScopes(), "Blank scopes should be normalized to null");
}
}
}
Loading