Skip to content

Commit 34d2b8d

Browse files
samsternbergclaude
andcommitted
feat: add JSON object support for ctx field in bearer token and signed data token generation
Extend the Java SDK's bearer token and signed data token generation to accept a JSON object (Map<String, Object>) for the ctx field, in addition to the existing String type. This enables structured context for conditional data access policies where ctx object keys map to Skyflow CEL policy variables (e.g., request.context.role, request.context.department). Changes: - Credentials: widen context field from String to Object, add overloaded setContext(Map<String, Object>) - BearerToken/SignedDataTokens: widen ctx to Object, add overloaded setCtx(Map<String, Object>), conditionally include ctx in JWT claims - Utils: dispatch to correct setCtx overload based on context type - Validations: validate both String and Map context types, validate map keys match [a-zA-Z0-9_]+ for CEL compatibility - ErrorMessage/ErrorLogs: add InvalidContextType and InvalidContextMapKey - Tests: add Map-based context tests for Credentials, BearerToken, and SignedDataTokens (51 tests, all passing) - Samples: add JSON object context examples - README: document both string and object ctx patterns with CEL policy variable mapping Technical note: JJWT's .claim(String, Object) handles both types — String serializes as a JSON string, Map serializes as a JSON object in the JWT payload. No custom serialization needed. Resolves: SK-2679, DOCU-1438 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9d69e23 commit 34d2b8d

File tree

13 files changed

+377
-202
lines changed

13 files changed

+377
-202
lines changed

README.md

Lines changed: 61 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -2771,73 +2771,59 @@ public class BearerTokenGenerationExample {
27712771

27722772
## Generate bearer tokens with context
27732773

2774-
**Context-aware authorization** embeds context values into a bearer token during its generation and so you can reference those values in your policies. This enables more flexible access controls, such as helping you track end-user identity when making API calls using service accounts, and facilitates using signed data tokens during detokenization. .
2774+
**Context-aware authorization** embeds context values into a bearer token during its generation so you can reference those values in your policies. This enables more flexible access controls, such as helping you track end-user identity when making API calls using service accounts, and facilitates using signed data tokens during detokenization.
27752775

27762776
A service account with the `context_id` identifier generates bearer tokens containing context information, represented as a JWT claim in a Skyflow-generated bearer token. Tokens generated from such service accounts include a `context_identifier` claim, are valid for 60 minutes, and can be used to make API calls to the Data and Management APIs, depending on the service account's permissions.
27772777

2778-
[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java):
2778+
The `setCtx()` method accepts either a **String** or a **`Map<String, Object>`**:
2779+
2780+
**String context** — use when your policy references a single context value:
27792781

27802782
```java
2781-
import com.skyflow.errors.SkyflowException;
2782-
import com.skyflow.serviceaccount.util.BearerToken;
2783+
BearerToken token = BearerToken.builder()
2784+
.setCredentials(new File(filePath))
2785+
.setCtx("user_12345")
2786+
.build();
2787+
```
27832788

2784-
import java.io.File;
2789+
**JSON object context** — use when your policy needs multiple context values for conditional data access. Each key in the `Map` maps to a Skyflow CEL policy variable under `request.context.*`:
27852790

2786-
/**
2787-
* Example program to generate a Bearer Token using Skyflow's BearerToken utility.
2788-
* The token is generated using two approaches:
2789-
* 1. By providing the credentials.json file path.
2790-
* 2. By providing the contents of credentials.json as a string.
2791-
*/
2792-
public class BearerTokenGenerationWithContextExample {
2793-
public static void main(String[] args) {
2794-
// Variable to store the generated Bearer Token
2795-
String bearerToken = null;
2791+
```java
2792+
Map<String, Object> ctx = new HashMap<>();
2793+
ctx.put("role", "admin");
2794+
ctx.put("department", "finance");
2795+
ctx.put("user_id", "user_12345");
27962796

2797-
// Approach 1: Generate Bearer Token by specifying the path to the credentials.json file
2798-
try {
2799-
// Replace <YOUR_CREDENTIALS_FILE_PATH> with the full path to your credentials.json file
2800-
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
2797+
BearerToken token = BearerToken.builder()
2798+
.setCredentials(new File(filePath))
2799+
.setCtx(ctx)
2800+
.build();
2801+
```
28012802

2802-
// Create a BearerToken object using the file path
2803-
BearerToken token = BearerToken.builder()
2804-
.setCredentials(new File(filePath)) // Set credentials using a File object
2805-
.setCtx("abc") // Set context string (example: "abc")
2806-
.build(); // Build the BearerToken object
2803+
With the map above, your Skyflow policies can reference `request.context.role`, `request.context.department`, and `request.context.user_id` to make conditional access decisions.
28072804

2808-
// Retrieve the Bearer Token as a string
2809-
bearerToken = token.getBearerToken();
2805+
You can also set context on `Credentials` for automatic token generation:
28102806

2811-
// Print the generated Bearer Token to the console
2812-
System.out.println(bearerToken);
2813-
} catch (SkyflowException e) {
2814-
// Handle exceptions specific to Skyflow operations
2815-
e.printStackTrace();
2816-
}
2807+
```java
2808+
// String context
2809+
Credentials credentials = new Credentials();
2810+
credentials.setPath("path/to/credentials.json");
2811+
credentials.setContext("user_12345");
28172812

2818-
// Approach 2: Generate Bearer Token by specifying the contents of credentials.json as a string
2819-
try {
2820-
// Replace <YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING> with the actual contents of your credentials.json file
2821-
String fileContents = "<YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING>";
2813+
// Map context
2814+
Map<String, Object> ctx = new HashMap<>();
2815+
ctx.put("role", "admin");
2816+
ctx.put("department", "finance");
2817+
credentials.setContext(ctx);
2818+
```
28222819

2823-
// Create a BearerToken object using the file contents as a string
2824-
BearerToken token = BearerToken.builder()
2825-
.setCredentials(fileContents) // Set credentials using a string representation of the file
2826-
.setCtx("abc") // Set context string (example: "abc")
2827-
.build(); // Build the BearerToken object
2820+
> **Note:** `getContext()` returns `Object` — callers should use `instanceof` if they need to inspect the type.
28282821
2829-
// Retrieve the Bearer Token as a string
2830-
bearerToken = token.getBearerToken();
2822+
Context map keys must contain only alphanumeric characters and underscores (`[a-zA-Z0-9_]`). Invalid keys will throw a `SkyflowException`.
28312823

2832-
// Print the generated Bearer Token to the console
2833-
System.out.println(bearerToken);
2834-
} catch (SkyflowException e) {
2835-
// Handle exceptions specific to Skyflow operations
2836-
e.printStackTrace();
2837-
}
2838-
}
2839-
}
2840-
```
2824+
[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java)
2825+
2826+
See Skyflow's [context-aware authorization](https://docs.skyflow.com) and [conditional data access](https://docs.skyflow.com) docs for policy variable syntax like `request.context.*`.
28412827

28422828
## Generate scoped bearer tokens
28432829

@@ -2903,58 +2889,31 @@ with the private key of the service account credentials, which adds an additiona
29032889
be detokenized by passing the signed data token and a bearer token generated from service account credentials. The
29042890
service account must have appropriate permissions and context to detokenize the signed data tokens.
29052891

2906-
[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java):
2892+
The `setCtx()` method on `SignedDataTokensBuilder` also accepts either a **String** or a **`Map<String, Object>`**, using the same format as bearer tokens:
29072893

29082894
```java
2909-
import com.skyflow.errors.SkyflowException;
2910-
import com.skyflow.serviceaccount.util.SignedDataTokenResponse;
2911-
import com.skyflow.serviceaccount.util.SignedDataTokens;
2912-
2913-
import java.io.File;
2914-
import java.util.ArrayList;
2915-
import java.util.List;
2916-
2917-
public class SignedTokenGenerationExample {
2918-
public static void main(String[] args) {
2919-
List<SignedDataTokenResponse> signedTokenValues;
2920-
// Generate Signed data token with context by specifying credentials.json file path
2921-
try {
2922-
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
2923-
String context = "abc";
2924-
ArrayList<String> dataTokens = new ArrayList<>();
2925-
dataTokens.add("YOUR_DATA_TOKEN_1");
2926-
SignedDataTokens signedToken = SignedDataTokens.builder()
2927-
.setCredentials(new File(filePath))
2928-
.setCtx(context)
2929-
.setTimeToLive(30) // in seconds
2930-
.setDataTokens(dataTokens)
2931-
.build();
2932-
signedTokenValues = signedToken.getSignedDataTokens();
2933-
System.out.println(signedTokenValues);
2934-
} catch (SkyflowException e) {
2935-
e.printStackTrace();
2936-
}
2937-
2938-
// Generate Signed data token with context by specifying credentials.json as string
2939-
try {
2940-
String fileContents = "<YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING>";
2941-
String context = "abc";
2942-
ArrayList<String> dataTokens = new ArrayList<>();
2943-
dataTokens.add("YOUR_DATA_TOKEN_1");
2944-
SignedDataTokens signedToken = SignedDataTokens.builder()
2945-
.setCredentials(fileContents)
2946-
.setCtx(context)
2947-
.setTimeToLive(30) // in seconds
2948-
.setDataTokens(dataTokens)
2949-
.build();
2950-
signedTokenValues = signedToken.getSignedDataTokens();
2951-
System.out.println(signedTokenValues);
2952-
} catch (SkyflowException e) {
2953-
e.printStackTrace();
2954-
}
2955-
}
2956-
}
2957-
```
2895+
// String context
2896+
SignedDataTokens signedToken = SignedDataTokens.builder()
2897+
.setCredentials(new File(filePath))
2898+
.setCtx("user_12345")
2899+
.setTimeToLive(30)
2900+
.setDataTokens(dataTokens)
2901+
.build();
2902+
2903+
// JSON object context
2904+
Map<String, Object> ctx = new HashMap<>();
2905+
ctx.put("role", "analyst");
2906+
ctx.put("department", "research");
2907+
2908+
SignedDataTokens signedToken = SignedDataTokens.builder()
2909+
.setCredentials(new File(filePath))
2910+
.setCtx(ctx)
2911+
.setTimeToLive(30)
2912+
.setDataTokens(dataTokens)
2913+
.build();
2914+
```
2915+
2916+
[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java)
29582917

29592918
Response:
29602919

samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,69 @@
44
import com.skyflow.serviceaccount.util.BearerToken;
55

66
import java.io.File;
7+
import java.util.HashMap;
8+
import java.util.Map;
79

810
/**
911
* Example program to generate a Bearer Token using Skyflow's BearerToken utility.
10-
* The token is generated using two approaches:
11-
* 1. By providing the credentials.json file path.
12-
* 2. By providing the contents of credentials.json as a string.
12+
* The token is generated using three approaches:
13+
* 1. By providing a string context.
14+
* 2. By providing a JSON object context (Map) for conditional data access policies.
15+
* 3. By providing the credentials as a string with context.
1316
*/
1417
public class BearerTokenGenerationWithContextExample {
1518
public static void main(String[] args) {
16-
// Variable to store the generated Bearer Token
1719
String bearerToken = null;
1820

19-
// Approach 1: Generate Bearer Token by specifying the path to the credentials.json file
21+
// Approach 1: Bearer token with string context
22+
// Use a simple string identifier when your policy references a single context value.
2023
try {
21-
// Replace <YOUR_CREDENTIALS_FILE_PATH> with the full path to your credentials.json file
2224
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
23-
24-
// Create a BearerToken object using the file path
2525
BearerToken token = BearerToken.builder()
26-
.setCredentials(new File(filePath)) // Set credentials using a File object
27-
.setCtx("abc") // Set context string (example: "abc")
28-
.build(); // Build the BearerToken object
26+
.setCredentials(new File(filePath))
27+
.setCtx("user_12345")
28+
.build();
2929

30-
// Retrieve the Bearer Token as a string
3130
bearerToken = token.getBearerToken();
32-
33-
// Print the generated Bearer Token to the console
34-
System.out.println(bearerToken);
31+
System.out.println("Bearer token (string context): " + bearerToken);
3532
} catch (SkyflowException e) {
36-
// Handle exceptions specific to Skyflow operations
3733
e.printStackTrace();
3834
}
3935

40-
// Approach 2: Generate Bearer Token by specifying the contents of credentials.json as a string
36+
// Approach 2: Bearer token with JSON object context
37+
// Use a structured Map when your policy needs multiple context values.
38+
// Each key maps to a Skyflow CEL policy variable under request.context.*
39+
// For example, the map below enables policies like:
40+
// request.context.role == "admin" && request.context.department == "finance"
4141
try {
42-
// Replace <YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING> with the actual contents of your credentials.json file
43-
String fileContents = "<YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING>";
42+
String filePath = "<YOUR_CREDENTIALS_FILE_PATH>";
43+
Map<String, Object> ctx = new HashMap<>();
44+
ctx.put("role", "admin");
45+
ctx.put("department", "finance");
46+
ctx.put("user_id", "user_12345");
4447

45-
// Create a BearerToken object using the file contents as a string
4648
BearerToken token = BearerToken.builder()
47-
.setCredentials(fileContents) // Set credentials using a string representation of the file
48-
.setCtx("abc") // Set context string (example: "abc")
49-
.build(); // Build the BearerToken object
49+
.setCredentials(new File(filePath))
50+
.setCtx(ctx)
51+
.build();
5052

51-
// Retrieve the Bearer Token as a string
5253
bearerToken = token.getBearerToken();
54+
System.out.println("Bearer token (object context): " + bearerToken);
55+
} catch (SkyflowException e) {
56+
e.printStackTrace();
57+
}
58+
59+
// Approach 3: Bearer token with string context from credentials string
60+
try {
61+
String fileContents = "<YOUR_CREDENTIALS_FILE_CONTENTS_AS_STRING>";
62+
BearerToken token = BearerToken.builder()
63+
.setCredentials(fileContents)
64+
.setCtx("user_12345")
65+
.build();
5366

54-
// Print the generated Bearer Token to the console
55-
System.out.println(bearerToken);
67+
bearerToken = token.getBearerToken();
68+
System.out.println("Bearer token (creds string): " + bearerToken);
5669
} catch (SkyflowException e) {
57-
// Handle exceptions specific to Skyflow operations
5870
e.printStackTrace();
5971
}
6072
}

0 commit comments

Comments
 (0)