Skip to content
Draft
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
30 changes: 30 additions & 0 deletions .github/workflows/markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Markdownlint

on:
pull_request:
branches:
- main
paths:
- "**/*.md"
- "**/*.mdx"
- ".markdownlint.yaml"

jobs:
markdownlint:
name: Markdownlint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get changed markdown files
id: changed
run: |
files=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | grep -E '\.(md|mdx)$' || true)
echo "files<<EOF" >> "$GITHUB_OUTPUT"
echo "$files" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
- uses: DavidAnson/markdownlint-cli2-action@v19
if: steps.changed.outputs.files != ''
with:
globs: ${{ steps.changed.outputs.files }}
30 changes: 30 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
MD024: # no-duplicate-heading
siblings_only: true
MD033: # no-inline-html
allowed_elements:
- br
- summary
- details
- strong
- Tabs
- TabItem
# MDX components (imported JSX — markdownlint can't distinguish from HTML)
- AssertionExamples
- CodeBlock
- CreateAttribute
- CreateConditionSet
- CreateNamespace
- CreateSubjectMapping
- DecryptOptions
- EncryptOptions
- GetDecisionsExample
- JsAuthNote
- ListAttributes
- ListNamespaces
- ListSubjectMapping
- SdkVersion
MD010: # no-hard-tabs
code_blocks: false # Go code uses tabs
MD013: false # Line length
MD041: false # First line heading — MDX files start with imports/frontmatter
MD060: true # Table column style
3 changes: 0 additions & 3 deletions code_samples/tdf/decrypt_options.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

## Decrypt Options

The following options can be passed to the decrypt call to control how the TDF is opened and validated.

---
Expand Down Expand Up @@ -371,4 +369,3 @@ Not available in the JavaScript SDK. The manifest size limit is not configurable

</TabItem>
</Tabs>

4 changes: 1 addition & 3 deletions code_samples/tdf/encrypt_options.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

## Encrypt Options

The following options can be passed to the encrypt call to control how the TDF is constructed.

---
Expand Down Expand Up @@ -513,7 +511,7 @@ const tdf = await client.createTDF({
</TabItem>
</Tabs>

Signed assertions can be verified on decrypt using [Assertion Verification Keys](#assertion-verification-keys).
Signed assertions can be verified on decrypt using [Assertion Verification Keys](/sdks/tdf#assertion-verification-keys).

---

Expand Down
146 changes: 85 additions & 61 deletions docs/sdks/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ sidebar_position: 2
title: Authentication
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Authentication
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

The SDKs authenticate with an [OIDC](https://openid.net/developers/how-connect-works/)-compatible identity provider (IdP) to obtain access tokens for the platform. The platform itself is a **resource server**, not an identity provider — you bring your own IdP (Keycloak is the reference implementation).

Expand Down Expand Up @@ -41,7 +39,7 @@ import io.opentdf.platform.sdk.SDKBuilder;
<TabItem value="js" label="JavaScript">

```typescript
import { authTokenInterceptor, OpenTDF } from '@opentdf/sdk';
import { authTokenInterceptor, OpenTDF } from "@opentdf/sdk";
```

</TabItem>
Expand Down Expand Up @@ -83,15 +81,23 @@ SDK sdk = new SDKBuilder()
<TabItem value="js" label="JavaScript">

```typescript
import { authTokenInterceptor, clientCredentialsTokenProvider, OpenTDF } from '@opentdf/sdk';
import {
authTokenInterceptor,
clientCredentialsTokenProvider,
OpenTDF,
} from "@opentdf/sdk";

const client = new OpenTDF({
interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
clientId: 'my-client-id',
clientSecret: 'my-client-secret',
oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
}))],
platformUrl: 'http://localhost:8080',
interceptors: [
authTokenInterceptor(
clientCredentialsTokenProvider({
clientId: "my-client-id",
clientSecret: "my-client-secret",
oidcOrigin: "http://localhost:8080/auth/realms/opentdf",
}),
),
],
platformUrl: "http://localhost:8080",
});
```

Expand Down Expand Up @@ -141,15 +147,23 @@ The Java SDK wraps the JWT as a `BearerAccessToken` and performs an RFC 8693 tok
<TabItem value="js" label="JavaScript">

```typescript
import { authTokenInterceptor, externalJwtTokenProvider, OpenTDF } from '@opentdf/sdk';
import {
authTokenInterceptor,
externalJwtTokenProvider,
OpenTDF,
} from "@opentdf/sdk";

const client = new OpenTDF({
interceptors: [authTokenInterceptor(externalJwtTokenProvider({
clientId: 'my-client-id',
externalJwt: 'eyJhbGciOi...',
oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
}))],
platformUrl: 'http://localhost:8080',
interceptors: [
authTokenInterceptor(
externalJwtTokenProvider({
clientId: "my-client-id",
externalJwt: "eyJhbGciOi...",
oidcOrigin: "http://localhost:8080/auth/realms/opentdf",
}),
),
],
platformUrl: "http://localhost:8080",
});
```

Expand All @@ -165,17 +179,25 @@ Refresh token authentication is currently available in the JavaScript SDK only.
:::

```typescript
import { authTokenInterceptor, refreshTokenProvider, OpenTDF } from '@opentdf/sdk';
import {
authTokenInterceptor,
refreshTokenProvider,
OpenTDF,
} from "@opentdf/sdk";

// Use a refresh token obtained from a prior OIDC login flow.
// The provider automatically exchanges it for access tokens and handles rotation.
const client = new OpenTDF({
interceptors: [authTokenInterceptor(refreshTokenProvider({
clientId: 'my-app',
refreshToken: 'refresh-token-from-login-flow',
oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
}))],
platformUrl: 'http://localhost:8080',
interceptors: [
authTokenInterceptor(
refreshTokenProvider({
clientId: "my-app",
refreshToken: "refresh-token-from-login-flow",
oidcOrigin: "http://localhost:8080/auth/realms/opentdf",
}),
),
],
platformUrl: "http://localhost:8080",
});
```

Expand All @@ -188,16 +210,18 @@ Access token authentication is currently available in the JavaScript SDK only. G
:::

```typescript
import { authTokenInterceptor, OpenTDF } from '@opentdf/sdk';
import { authTokenInterceptor, OpenTDF } from "@opentdf/sdk";

// Pass any () => Promise<string> function that returns a valid access token.
// Example: read from your OIDC library's user manager.
const client = new OpenTDF({
interceptors: [authTokenInterceptor(async () => {
const user = await userManager.getUser();
return user?.access_token ?? '';
})],
platformUrl: 'http://localhost:8080',
interceptors: [
authTokenInterceptor(async () => {
const user = await userManager.getUser();
return user?.access_token ?? "";
}),
],
platformUrl: "http://localhost:8080",
});
```

Expand Down Expand Up @@ -269,7 +293,7 @@ For advanced integrations where the built-in auth methods don't fit, you can **b
<Tabs>
<TabItem value="go" label="Go">

**Option A: Standard OAuth2 token source**
### Option A: Standard OAuth2 token source

```go
import "golang.org/x/oauth2"
Expand All @@ -283,7 +307,7 @@ client, err := sdk.New("http://localhost:8080",
)
```

**Option B: Implement the `AccessTokenSource` interface**
### Option B: Implement the `AccessTokenSource` interface

For full control, implement the `auth.AccessTokenSource` interface:

Expand Down Expand Up @@ -317,26 +341,26 @@ SDK sdk = new SDKBuilder()
Write a custom interceptor for full control over request authentication. This is useful when your app already has its own auth system (e.g., an OIDC library like `oidc-client-ts`, Auth0, or a custom token store):

```typescript
import { type Interceptor } from '@connectrpc/connect';
import { OpenTDF } from '@opentdf/sdk';
import { type Interceptor } from "@connectrpc/connect";
import { OpenTDF } from "@opentdf/sdk";

// Replace this with however your app obtains tokens —
// e.g., from an OIDC library, auth context, or token store.
async function getMyToken(): Promise<string> {
// Example: read from your OIDC library's user manager
// const user = await userManager.getUser();
// return user?.access_token ?? '';
throw new Error('Implement getMyToken() for your auth system');
// Example: read from your OIDC library's user manager
// const user = await userManager.getUser();
// return user?.access_token ?? '';
throw new Error("Implement getMyToken() for your auth system");
}

const myAuthInterceptor: Interceptor = (next) => async (req) => {
req.header.set('Authorization', `Bearer ${await getMyToken()}`);
return next(req);
req.header.set("Authorization", `Bearer ${await getMyToken()}`);
return next(req);
};

const client = new OpenTDF({
interceptors: [myAuthInterceptor],
platformUrl: 'http://localhost:8080',
interceptors: [myAuthInterceptor],
platformUrl: "http://localhost:8080",
});
```

Expand All @@ -347,11 +371,11 @@ const client = new OpenTDF({

[DPoP (Demonstration of Proof-of-Possession)](https://datatracker.ietf.org/doc/html/rfc9449) binds access tokens to a cryptographic key pair, preventing token theft and replay attacks. The SDKs handle DPoP automatically in most cases.

| SDK | Default behavior | Customization |
|-----|-----------------|---------------|
| **Go** | DPoP key auto-generated | `sdk.WithSessionSignerRSA(key)` to provide your own RSA key |
| **Java** | Always on (RSA, auto-generated) | `SDKBuilder.srtSigner(signer)` for custom signing |
| **JavaScript** | Off by default with interceptors | Use `authTokenDPoPInterceptor()` to enable |
| SDK | Default behavior | Customization |
| -------------- | -------------------------------- | ----------------------------------------------------------- |
| **Go** | DPoP key auto-generated | `sdk.WithSessionSignerRSA(key)` to provide your own RSA key |
| **Java** | Always on (RSA, auto-generated) | `SDKBuilder.srtSigner(signer)` for custom signing |
| **JavaScript** | Off by default with interceptors | Use `authTokenDPoPInterceptor()` to enable |

<Tabs>
<TabItem value="go" label="Go">
Expand Down Expand Up @@ -387,18 +411,18 @@ SDK sdk = new SDKBuilder()
DPoP is off by default with interceptors. Use `authTokenDPoPInterceptor()` to enable it:

```typescript
import { authTokenDPoPInterceptor, OpenTDF } from '@opentdf/sdk';
import { authTokenDPoPInterceptor, OpenTDF } from "@opentdf/sdk";

// Use any TokenProvider: refreshTokenProvider(), clientCredentialsTokenProvider(),
// externalJwtTokenProvider(), or a custom () => Promise<string> function.
const dpopInterceptor = authTokenDPoPInterceptor({
tokenProvider: getAccessToken, // your token provider here
tokenProvider: getAccessToken, // your token provider here
});

const client = new OpenTDF({
interceptors: [dpopInterceptor],
dpopKeys: dpopInterceptor.dpopKeys,
platformUrl: 'http://localhost:8080',
interceptors: [dpopInterceptor],
dpopKeys: dpopInterceptor.dpopKeys,
platformUrl: "http://localhost:8080",
});
```

Expand All @@ -421,18 +445,18 @@ The legacy `AuthProvider` pattern managed token lifecycle internally:
<TabItem value="js" label="JavaScript">

```typescript
import { AuthProviders, OpenTDF } from '@opentdf/sdk';
import { AuthProviders, OpenTDF } from "@opentdf/sdk";

const authProvider = await AuthProviders.clientSecretAuthProvider({
clientId: 'my-client-id',
clientSecret: 'my-client-secret',
oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
exchange: 'client',
clientId: "my-client-id",
clientSecret: "my-client-secret",
oidcOrigin: "http://localhost:8080/auth/realms/opentdf",
exchange: "client",
});

const client = new OpenTDF({
authProvider,
platformUrl: 'http://localhost:8080',
authProvider,
platformUrl: "http://localhost:8080",
});
await client.ready;
```
Expand Down
Loading
Loading