hyperdb-api-salesforce 0.1.1

Salesforce Data Cloud OAuth authentication for Hyper database
Documentation

hyperdb-api-salesforce

Salesforce Data Cloud OAuth authentication for Hyper database.

This crate implements the two-stage OAuth token flow for connecting to Salesforce Data Cloud via gRPC. It supports the JWT Bearer Token Flow, username-password flow, and refresh token flow, with automatic token caching and refresh.


Overview

hyperdb-api-salesforce is a companion crate that provides Salesforce-specific authentication, separate from the core hyperdb-api and hyperdb-api-core::client crates.

  • Three OAuth flows -- JWT Bearer Token (recommended), username-password, refresh token
  • Automatic token refresh -- Proactive refresh before expiration, reactive on auth errors
  • Thread-safe -- SharedTokenProvider for concurrent access via Arc<Mutex<>>
  • Secure memory -- Private keys and secrets stored with zeroize
  • gRPC integration -- Works with AuthenticatedGrpcClient from hyperdb-api-core::client

Quick Start (JWT Bearer Token)

use hyperdb_api_salesforce::{SalesforceAuthConfig, AuthMode, DataCloudTokenProvider};

let private_key_pem = std::fs::read_to_string("private.key")?;

let config = SalesforceAuthConfig::new(
    "https://login.salesforce.com",
    "your-connected-app-consumer-key",
)?
.auth_mode(AuthMode::private_key("user@example.com", &private_key_pem)?);

let mut provider = DataCloudTokenProvider::new(config)?;
let token = provider.get_token().await?;

println!("Bearer token: {}", token.bearer_token());
println!("Tenant URL: {}", token.tenant_url_str());

Authentication Flows

JWT Bearer Token (Recommended)

Best for server-to-server authentication. Does not require a client secret.

let config = SalesforceAuthConfig::new(login_url, client_id)?
    .auth_mode(AuthMode::private_key("user@example.com", &private_key_pem)?);
Parameter Description
login_url Salesforce login URL (e.g., https://login.salesforce.com)
client_id External Client App Consumer Key
username Salesforce username that authorized the app
private_key_pem RSA private key in PKCS#8 PEM format

Username-Password

Requires client_secret. The password may need a security token suffix.

let config = SalesforceAuthConfig::new(login_url, client_id)?
    .client_secret("your-consumer-secret")
    .auth_mode(AuthMode::password("user@example.com", "password+securitytoken"));

Refresh Token

Uses a long-lived OAuth refresh token. Requires client_secret.

let config = SalesforceAuthConfig::new(login_url, client_id)?
    .client_secret("your-consumer-secret")
    .auth_mode(AuthMode::refresh_token("your-refresh-token"));

Token Management

DataCloudTokenProvider caches both the OAuth Access Token and the DC JWT independently. The OAuth Access Token (~2-hour lifetime) is only refreshed when genuinely expired, avoiding unnecessary refresh token rotation. The DC JWT is refreshed proactively based on both expiry and age. get_token() returns a cached token when valid, or transparently performs the full flow when needed.

SharedTokenProvider

For concurrent applications, wrap the provider in SharedTokenProvider:

use hyperdb_api_salesforce::SharedTokenProvider;

let provider = SharedTokenProvider::new(config)?;

// Clone and share across tasks
let provider_clone = provider.clone();
tokio::spawn(async move {
    let token = provider_clone.get_token().await?;
    // use token.bearer_token() as the Authorization header
});

SharedTokenProvider uses Arc<Mutex<DataCloudTokenProvider>> internally. All token operations are serialized to prevent concurrent refresh races.


Usage with AuthenticatedGrpcClient

Enable the salesforce-auth feature on hyperdb-api-core::client:

[dependencies]
hyperdb-api-core = { version = "0.1", features = ["salesforce-auth"] }
hyperdb-api-salesforce = "0.1"

AuthenticatedGrpcClient handles token refresh automatically, including reconnection with new tokens:

use hyperdb_api_salesforce::{SalesforceAuthConfig, AuthMode, SharedTokenProvider};
use hyperdb_api_core::client::grpc::AuthenticatedGrpcClient;

let private_key_pem = std::fs::read_to_string("private.key")?;
let auth_config = SalesforceAuthConfig::new(login_url, client_id)?
    .auth_mode(AuthMode::private_key("user@example.com", &private_key_pem)?);

let token_provider = SharedTokenProvider::new(auth_config)?;
let mut client = AuthenticatedGrpcClient::connect(token_provider, None).await?;

// Token refresh is handled automatically, even for long-running queries
let result = client.execute_query("SELECT * FROM my_table").await?;

// Built-in catalog operations
let tables = client.list_tables().await?;

Refresh strategy:

  1. Proactive -- Refreshes the DC JWT before expiration (5-minute buffer) and when it exceeds a maximum age (15 minutes by default)
  2. Reactive -- On gRPC auth errors (UNAUTHENTICATED), refreshes and retries once

Setup Guide

Step 1: Generate RSA Key Pair

# Generate a 2048-bit RSA private key
openssl genrsa -out keypair.key 2048

# Create a self-signed certificate (valid 1 year)
openssl req -new -x509 -nodes -sha256 -days 365 -key keypair.key -out certificate.crt

# Convert to PKCS#8 format (required by this crate)
openssl pkcs8 -topk8 -nocrypt -in keypair.key -out private.key
  • certificate.crt -- Upload to Salesforce External Client App
  • private.key -- Use in your application (keep secure)

Step 2: Create External Client App in Salesforce

  1. Setup > External Client App Manager > New External Client App
  2. Flow Type: App acts on behalf of a user
  3. Add OAuth scopes:
    • Access the identity URL service (id, profile, email, address, phone)
    • Manage user data via APIs (api)
    • Perform requests at any time (refresh_token, offline_access)
    • Perform ANSI SQL queries on Data Cloud data (cdp_query_api) -- required
  4. Callback URL: https://localhost:1717/OauthRedirect
  5. Enable JWT Bearer Flow and upload certificate.crt

Step 3: Pre-authorize Users

  1. External Client App Manager > Your app > Policies tab
  2. Set Permitted Users to Admin approved users are pre-authorized
  3. Manage Profiles > Add profiles that should have access

Step 4: Get Consumer Key

  1. External Client App Manager > Your app > OAuth Settings
  2. Click Manage Consumer Details (verify identity)
  3. Copy the Consumer Key (this is the client_id)

Environment Variables

The included example reads configuration from environment variables:

export SF_LOGIN_URL="https://login.salesforce.com"
export SF_CLIENT_ID="3MVG9..."
export SF_USERNAME="your-email@company.com"
export SF_PRIVATE_KEY_PATH="/path/to/private.key"
export SF_DATASPACE="default"  # optional
cargo run -p hyperdb-api-salesforce --example salesforce_auth_example

Configuration Options

Builder method Default Description
.client_secret(s) None Required for Password and RefreshToken modes
.dataspace(s) None Data Cloud dataspace name
.timeout_secs(n) 30 HTTP request timeout
.max_retries(n) 3 Max retries for transient (5xx) failures

Login URLs: https://login.salesforce.com (production), https://test.salesforce.com (sandbox), or https://mydomain.my.salesforce.com (custom domain).


Troubleshooting

Error Cause and Solution
invalid_grant: user hasn't approved this consumer Pre-authorize the user (Step 3 above)
invalid_client_id Verify Consumer Key matches exactly
Private key error: failed to parse private key Convert to PKCS#8: openssl pkcs8 -topk8 -nocrypt -in keypair.key -out private.key
invalid_grant: authentication failure Check username, login URL, and certificate match
DC JWT exchange failed Verify Data Cloud license and cdp_query_api scope
client_secret is required for Password and RefreshToken auth modes Add .client_secret(...) to your config

License

Apache-2.0 — see LICENSE-APACHE.