Stacked on #4173 (`jack/restore-oidc-flows`, base branch mirrored to this repo so the diff shows only this change); context from review: https://github.com/lancedb/lancedb/pull/4173#issuecomment-5674048100. Rebase to `main` once #4173 and #4179 merge. ## What moved to the `oauth2` crate (5.0, no default features) - Authorization URL generation and CSRF state (`authorize_url`, `CsrfToken`) - PKCE S256 challenge/verifier generation and code exchange - Client-credentials, authorization-code, refresh-token, and device-code grant request construction - Device authorization request and the device token polling loop (`authorization_pending`, `slow_down` +5s, expiry deadline, denial, network backoff capped at 10s) - Standard success/error response parsing (`RequestTokenError`) - Token-endpoint client authentication and standards-compliant parameter encoding (RFC 6749 2.3.1 Basic encoding) LanceDB keeps ownership of OIDC discovery (compared `openidconnect`: no measurable win for our 3-field metadata + strict validation, at real dependency cost), HTTPS-or-loopback endpoint enforcement, the loopback callback server, browser/stderr prompts, token caching and refresh orchestration, and the dedicated hardened Azure IMDS source, which is unchanged. ## Client authentication methods New `ClientAuthMethod` enum (`none` | `client_secret_basic` | `client_secret_post`), exposed in Rust, Python, and Node. Unset resolves to `client_secret_basic` when a secret is present (RFC 6749 2.3.1 recommendation and the normal Okta confidential-app default, so a default Okta app works without weakening its configuration) and to `none` for public clients (PKCE/device). Explicit `none` with a secret, or basic/post without one, is rejected. The method applies to client credentials, code exchange, refresh, and device requests. Deliberate behavior change: confidential clients previously always sent the secret in the POST body; they now default to Basic (Keycloak accepts both). No `audience`/`resource` parameters were added: the supported target is an Okta custom authorization server with the API audience configured server-side, so client-provided audience parameters are unnecessary; `add_extra_param` support exists if a concrete provider contract ever needs them. ## Device polling behavior changes (deliberate, tested) - The first token poll now happens immediately rather than after one interval (RFC 8628 allows both). - Transient failures (HTTP 429, 5xx, `temporarily_unavailable`, network errors) now retry with exponential backoff capped at 10s instead of retrying at the fixed interval; polling never spins faster than once per second even if a server reports a zero interval. ## Security and compatibility - Issuer and discovered endpoints (and device verification URIs) still require HTTPS except explicit loopback HTTP, enforced before any crate URL type is built - Token HTTP client keeps the hardened redirect policy that refuses insecure redirect targets; regression test added - Errors never embed raw response bodies (avoids leaking tokens through parse failures); all credential types stay redacted in Debug - Transient conditions (429/5xx/`temporarily_unavailable`) remain retryable in device polling and hard errors elsewhere; refresh keeps rotation and reauthentication semantics - Existing public APIs stay source-compatible except the added `OAuthConfig.client_auth_method` field ## Tests Rust: client-auth methods across code exchange/refresh/client-credentials/device (none/basic/post), auth-method resolution and validation, transient device retries, denial/expiry, redirect rejection, malformed-response leak check, PKCE URL assertions, redaction. Python and Node: enum values, conversion, unknown-method errors, config defaults. Manual Okta validation recipe (no automated Okta credentials): create a custom authorization server with an API audience, one confidential web app (Basic) for authorization-code, one native app (PKCE, no secret), one native device app; point `issuer_url` at the custom server, set `client_auth_method` only for the POST-required case; verify token acquisition, refresh after expiry, and `x-lancedb-credential-type: oidc` against a LanceDB deployment. Never commit tenant URLs or secrets. Co-authored-by: Xuanwo <github@xuanwo.io>
4.2 KiB
@lancedb/lancedb • Docs
@lancedb/lancedb / OAuthConfig
Interface: OAuthConfig
OAuth configuration for LanceDB authentication.
This is the public TypeScript OAuth configuration type. The generated
NativeOAuthConfig type has the same runtime shape but is an implementation
detail of the napi-rs binding.
All token acquisition and refresh is handled in the Rust layer. This config is passed through to Rust via napi-rs.
Examples
const config: OAuthConfig = {
issuerUrl: "https://login.microsoftonline.com/{tenant}/v2.0",
clientId: "app-id",
clientSecret: "secret",
scopes: ["api://lancedb-api/.default"],
};
Providers requiring an explicit target can set resource and/or audience:
const targeted: OAuthConfig = {
issuerUrl: "https://issuer.example.com",
clientId: "app-id",
clientSecret: "secret",
scopes: ["read"],
resource: "https://api.example.com",
audience: "lancedb-api",
};
const config: OAuthConfig = {
issuerUrl: "https://login.microsoftonline.com/{tenant}/v2.0",
clientId: "app-id",
scopes: ["api://lancedb-api/.default"],
flow: OAuthFlowType.AzureManagedIdentity,
};
The authorization URL is written to stderr before LanceDB tries to open a browser, so it can be copied in headless environments.
const config: OAuthConfig = {
issuerUrl: "https://login.microsoftonline.com/{tenant}/v2.0",
clientId: "app-id",
scopes: ["openid", "api://lancedb-api/access"],
flow: OAuthFlowType.AuthorizationCode,
};
Device Authorization writes the verification URL and user code to stderr before polling begins.
Properties
audience?
optional audience: string;
Provider-specific audience, forwarded to authorization and token endpoints, including refresh requests. Not supported for Azure managed identity.
callbackPort?
optional callbackPort: number;
Port for the AuthorizationCode loopback callback server (default: 8400).
clientAuthMethod?
optional clientAuthMethod: ClientAuthMethod;
How the client authenticates to the token endpoint (default: auto).
With a clientSecret the default is ClientAuthMethod.ClientSecretBasic,
which matches the RFC 6749 recommendation and the default configuration
of Okta confidential applications; without a secret the client is public
and no client authentication is sent.
clientId
clientId: string;
Application / Client ID.
clientSecret?
optional clientSecret: string;
Client secret (required for ClientCredentials).
flow?
optional flow: OAuthFlowType;
Authentication flow (default: ClientCredentials).
issuerUrl
issuerUrl: string;
OIDC issuer URL or OAuth authority URL.
For Azure: https://login.microsoftonline.com/{tenant_id}/v2.0
managedIdentityClientId?
optional managedIdentityClientId: string;
Client ID for user-assigned managed identity (AzureManagedIdentity).
redirectUri?
optional redirectUri: string;
Loopback redirect URI for AuthorizationCode.
refreshBufferSecs?
optional refreshBufferSecs: number;
Seconds before expiry to trigger proactive refresh (default: 300). Keep this well below the token TTL; if it is greater than or equal to the TTL, each request refreshes the token.
resource?
optional resource: string;
Resource indicator (RFC 8707), forwarded verbatim to authorization and token endpoints, including refresh requests. Must be an absolute URI without a fragment. Not supported for Azure managed identity.
scopes
scopes: string[];
OAuth scopes to request.
For Azure managed identity, exactly one scope or resource is required.
For example: ["api://{app_id}/.default"]
tokenCache?
optional tokenCache: TokenCacheOptions;
Opt in to the persistent token cache so short-lived processes reuse one session. Only refresh tokens are persisted. Only supported by AuthorizationCode and DeviceCode; Azure managed identity is rejected. Default: unset (memory only).
usePkce?
optional usePkce: boolean;
Protect AuthorizationCode with S256 PKCE (default: true).