Skip to main content

Authentication

The SDKs authenticate with an OIDC-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).

Not sure which method to use?

The JavaScript SDK is designed for browser applications. If your app already has an access token, use Access Token. If your OIDC flow provides a refresh token, use Refresh Token for automatic renewal. For backend scripts and testing, see Client Credentials. See the Authentication Decision Guide for a full comparison.

Setup

All examples on this page assume you have installed the SDK and imported the base packages:

import (
"log"

"github.com/opentdf/platform/sdk"
)

Client Credentials

The most common authentication method for backend services, scripts, and CI/CD pipelines. Uses an OAuth2 client ID and secret to obtain tokens directly from the IdP.

client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("my-client-id", "my-client-secret", nil),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()

The third parameter accepts optional scopes ([]string). Pass nil to use the IdP defaults.

Token lifecycle: The built-in token providers automatically cache tokens and refresh them when they expire (with a 30-second buffer). You can also write your own TokenProvider — any () => Promise<string> function works.

Token Exchange

Use token exchange (RFC 8693) when you already have a token from another identity system and need to exchange it for one the platform accepts. Common in federated identity and SAML environments.

client, err := sdk.New("http://localhost:8080",
sdk.WithTokenExchange("eyJhbGciOi...", []string{"target-audience"}),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
  • subjectToken — the existing JWT or SAML assertion to exchange
  • audience — the target audience(s) for the new token

Refresh Token

Use a refresh token for browser-based applications and long-lived sessions where you need to maintain access without re-prompting the user.

JavaScript SDK only

Refresh token authentication is currently available in the JavaScript SDK only. Go and Java SDKs handle token lifecycle differently — see Client Credentials for backend use cases.

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',
});

Access Token

Use an access token when your application already has a valid token from its own authentication system — for example, from an OIDC library like oidc-client-ts, an auth context provider, or a cookie. This is common when the IdP doesn't issue refresh tokens or when your security policy restricts their use.

JavaScript SDK only

Access token authentication is currently available in the JavaScript SDK only. Go and Java SDKs handle token lifecycle differently — see Client Credentials for backend use cases, or Custom Token Source for advanced integrations.

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',
});
You manage token lifecycle

Unlike the built-in token providers (clientCredentialsTokenProvider, refreshTokenProvider), a custom function does not automatically cache or refresh tokens. If your tokens are short-lived, ensure your function returns a fresh token when called — authTokenInterceptor calls it on every request.

Certificate Exchange (mTLS)

Use certificate-based authentication in environments that require mutual TLS (mTLS) with client certificates.

import "crypto/tls"

tlsConfig := &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: caCertPool,
}

client, err := sdk.New("https://platform.example.com",
sdk.WithTLSCredentials(tlsConfig, []string{"target-audience"}),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()

Custom Token Source

For advanced integrations where the built-in auth methods don't fit, you can bring your own token provider.

Option A: Standard OAuth2 token source

import "golang.org/x/oauth2"

tokenSource := oauth2.StaticTokenSource(&oauth2.Token{
AccessToken: "pre-obtained-token",
})

client, err := sdk.New("http://localhost:8080",
sdk.WithOAuthAccessTokenSource(tokenSource),
)

Option B: Implement the AccessTokenSource interface

For full control, implement the auth.AccessTokenSource interface:

type AccessTokenSource interface {
AccessToken(ctx context.Context, client *http.Client) (AccessToken, error)
MakeToken(func(jwk.Key) ([]byte, error)) ([]byte, error)
}

DPoP (Sender-Constrained Tokens)

DPoP (Demonstration of Proof-of-Possession) binds access tokens to a cryptographic key pair, preventing token theft and replay attacks. The SDKs handle DPoP automatically in most cases.

SDKDefault behaviorCustomization
GoDPoP key auto-generatedsdk.WithSessionSignerRSA(key) to provide your own RSA key
JavaAlways on (RSA, auto-generated)SDKBuilder.srtSigner(signer) for custom signing
JavaScriptOff by default with interceptorsUse authTokenDPoPInterceptor() to enable

DPoP is enabled by default with an auto-generated key. To provide your own RSA key:

import "crypto/rsa"

// Provide your own RSA key for DPoP signing
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("my-client-id", "my-client-secret", nil),
sdk.WithSessionSignerRSA(myRSAPrivateKey), // *rsa.PrivateKey
)
warning

Only disable DPoP if your IdP does not support it. DPoP is recommended for production deployments as it provides protection against token exfiltration.

Legacy: AuthProvider

info

The AuthProvider interface is deprecated as of the interceptor-based auth introduced in @opentdf/sdk. Existing code using AuthProvider continues to work. For new code, use interceptors as shown above.

The legacy AuthProvider pattern managed token lifecycle internally:

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',
});

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