From 17d48d19a584b0dce7607616cab47b584dfe5163 Mon Sep 17 00:00:00 2001 From: Sam Sternberg Date: Mon, 30 Mar 2026 14:28:28 -0400 Subject: [PATCH] docs: document ctx JSON object support for bearer and signed data tokens The Node SDK already supports ctx as string | Record (shipped in SK-2391), but the README and samples only showed string usage. This adds: - README: code examples for both string and object ctx patterns - README: explains how ctx keys map to Skyflow CEL policy variables (request.context.role, request.context.department, etc.) - README: documents context field on PathCredentials/StringCredentials - README: signed data tokens section now shows object ctx usage - Samples: bearer token example shows string, object, and creds approaches - Samples: signed token example shows string and object approaches Resolves: DOCU-1441 Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 74 ++++++++++++++++++- .../signed-token-generation-example.ts | 50 +++++++++++-- .../token-generation-with-context-example.ts | 45 ++++++++++- 3 files changed, 153 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a1bc7449..cb9f1aa2 100644 --- a/README.md +++ b/README.md @@ -873,18 +873,84 @@ const options = { Embed context values into a bearer token during generation so you can reference those values in your policies. This enables more flexible access controls, such as tracking end-user identity when making API calls using service accounts, and facilitates using signed data tokens during detokenization. -Generate bearer tokens containing context information using a service account with the context_id identifier. Context information is 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. +Generate bearer tokens containing context information using a service account with the `context_id` identifier. Context information is 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. + +The `ctx` parameter accepts either a **string** or a **JSON object**: + +**String context** — use when your policy references a single context value: + +```typescript +const options = { + ctx: 'user_12345', +}; +const response = await generateBearerToken(filepath, options); +``` + +**JSON object context** — use when your policy needs multiple context values for conditional data access. Each key in the `ctx` object maps to a Skyflow CEL policy variable under `request.context.*`: + +```typescript +const options = { + ctx: { + role: 'admin', + department: 'finance', + user_id: 'user_12345', + }, +}; +const response = await generateBearerToken(filepath, options); +``` + +With the object above, your Skyflow policies can reference `request.context.role`, `request.context.department`, and `request.context.user_id` to make conditional access decisions. + +You can also set the `context` field on credentials for automatic token generation: + +```typescript +// String context on credentials +const credentials: PathCredentials = { + path: 'path/to/credentials.json', + context: 'user_12345', +}; + +// JSON object context on credentials +const credentials: PathCredentials = { + path: 'path/to/credentials.json', + context: { + role: 'admin', + department: 'finance', + }, +}; +``` > [!TIP] -> See the full example in the samples directory: [token-generation-with-context-example.ts](samples/service-account/token-generation-with-context-example.ts) -> See [docs.skyflow.com](https://docs.skyflow.com) for more details on authentication, access control, and governance for Skyflow. +> See the full example in the samples directory: [token-generation-with-context-example.ts](samples/service-account/token-generation-with-context-example.ts) +> 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.*`. #### Generate signed data tokens: `generateSignedDataTokens(filepath, options)` Digitally sign data tokens with a service account's private key to add an extra layer of protection. Skyflow generates data tokens when sensitive data is inserted into the vault. Detokenize signed tokens only by providing the signed data token along with a bearer token generated from the service account's credentials. The service account must have the necessary permissions and context to successfully detokenize the signed data tokens. +The `ctx` parameter on signed data tokens also accepts either a **string** or a **JSON object**, using the same format as bearer tokens: + +```typescript +// String context +const options = { + ctx: 'user_12345', + dataTokens: ['dataToken1', 'dataToken2'], + timeToLive: 90, +}; + +// JSON object context +const options = { + ctx: { + role: 'analyst', + department: 'research', + }, + dataTokens: ['dataToken1', 'dataToken2'], + timeToLive: 90, +}; +``` + > [!TIP] -> See the full example in the samples directory: [signed-token-generation-example.ts](samples/service-account/signed-token-generation-example.ts) +> See the full example in the samples directory: [signed-token-generation-example.ts](samples/service-account/signed-token-generation-example.ts) > See [docs.skyflow.com](https://docs.skyflow.com) for more details on authentication, access control, and governance for Skyflow. ## Logging diff --git a/samples/service-account/signed-token-generation-example.ts b/samples/service-account/signed-token-generation-example.ts index e717967b..356106c5 100644 --- a/samples/service-account/signed-token-generation-example.ts +++ b/samples/service-account/signed-token-generation-example.ts @@ -17,11 +17,12 @@ let cred = { privateKey: '', }; -function getSignedTokenFromFilePath() { +// Approach 1: Signed data tokens with string context +function getSignedTokenWithStringContext() { return new Promise(async (resolve, reject) => { try { const options = { - ctx: 'ctx', + ctx: 'user_12345', dataTokens: ['dataToken1', 'dataToken2'], timeToLive: 90 // In seconds. }; @@ -33,6 +34,30 @@ function getSignedTokenFromFilePath() { }); } +// Approach 2: Signed data tokens with JSON object context +// Each key in the ctx object maps to a Skyflow CEL policy variable under request.context.* +// For example: request.context.role == "analyst" && request.context.department == "research" +function getSignedTokenWithObjectContext() { + return new Promise(async (resolve, reject) => { + try { + const options = { + ctx: { + role: 'analyst', + department: 'research', + user_id: 'user_67890', + }, + dataTokens: ['dataToken1', 'dataToken2'], + timeToLive: 90, // In seconds. + }; + let response = await generateSignedDataTokens(filepath, options); + resolve(response); + } catch (e) { + reject(e); + } + }); +} + +// Approach 3: Signed data tokens from credentials string function getSignedTokenFromCreds() { return new Promise(async (resolve, reject) => { try { @@ -54,16 +79,25 @@ function getSignedTokenFromCreds() { const tokens = async () => { try { - const tokenResponseFromFilePath: any = await getSignedTokenFromFilePath(); - tokenResponseFromFilePath.forEach((response) => { - console.log(`Data Token: ${response.token}`); - console.log(`Signed Data Token: ${response.signedToken}`); + const tokenResponseString: any = await getSignedTokenWithStringContext(); + console.log('Signed tokens (string context):'); + tokenResponseString.forEach((response) => { + console.log(` Data Token: ${response.token}`); + console.log(` Signed Data Token: ${response.signedToken}`); + }); + + const tokenResponseObject: any = await getSignedTokenWithObjectContext(); + console.log('Signed tokens (object context):'); + tokenResponseObject.forEach((response) => { + console.log(` Data Token: ${response.token}`); + console.log(` Signed Data Token: ${response.signedToken}`); }); const tokenResponseFromCreds: any = await getSignedTokenFromCreds(); + console.log('Signed tokens (from creds):'); tokenResponseFromCreds.forEach((response) => { - console.log(`Data Token: ${response.token}`); - console.log(`Signed Data Token: ${response.signedToken}`); + console.log(` Data Token: ${response.token}`); + console.log(` Signed Data Token: ${response.signedToken}`); }); } catch (error) { console.log(error); diff --git a/samples/service-account/token-generation-with-context-example.ts b/samples/service-account/token-generation-with-context-example.ts index 308898d1..78de877e 100644 --- a/samples/service-account/token-generation-with-context-example.ts +++ b/samples/service-account/token-generation-with-context-example.ts @@ -19,11 +19,46 @@ const cred = { privateKey: '', }; -function getSkyflowBearerTokenWithContextFromFilePath() { +// Approach 1: Bearer token with string context +// Use a simple string identifier when your policy references a single context value. +// In your Skyflow policy, reference this as: request.context +function getSkyflowBearerTokenWithStringContext() { return new Promise((resolve, reject) => { try { const options = { - ctx: 'context_id', + ctx: 'user_12345', + }; + if (!isExpired(bearerToken)) resolve(bearerToken); + else { + generateBearerToken(filepath, options) + .then(response => { + bearerToken = response.accessToken; + resolve(bearerToken); + }) + .catch(error => { + reject(error); + }); + } + } catch (e) { + reject(e); + } + }); +} + +// Approach 2: Bearer token with JSON object context +// Use a structured object when your policy needs multiple context values. +// Each key maps to a Skyflow CEL policy variable under request.context.* +// For example, the object below enables policies like: +// request.context.role == "admin" && request.context.department == "finance" +function getSkyflowBearerTokenWithObjectContext() { + return new Promise((resolve, reject) => { + try { + const options = { + ctx: { + role: 'admin', + department: 'finance', + user_id: 'user_12345', + }, }; if (!isExpired(bearerToken)) resolve(bearerToken); else { @@ -42,6 +77,7 @@ function getSkyflowBearerTokenWithContextFromFilePath() { }); } +// Approach 3: Bearer token with string context from credentials string function getSkyflowBearerTokenWithContextFromCreds() { return new Promise((resolve, reject) => { try { @@ -66,8 +102,9 @@ function getSkyflowBearerTokenWithContextFromCreds() { } const tokens = async () => { - console.log(await getSkyflowBearerTokenWithContextFromFilePath()); - console.log(await getSkyflowBearerTokenWithContextFromCreds()); + console.log('String context:', await getSkyflowBearerTokenWithStringContext()); + console.log('Object context:', await getSkyflowBearerTokenWithObjectContext()); + console.log('Creds string context:', await getSkyflowBearerTokenWithContextFromCreds()); }; tokens();