Skip to content
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
2 changes: 0 additions & 2 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
2 changes: 0 additions & 2 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
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