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.
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 --
SharedTokenProviderfor concurrent access viaArc<Mutex<>> - Secure memory -- Private keys and secrets stored with
zeroize - gRPC integration -- Works with
AuthenticatedGrpcClientfromhyperdb-api-core::client
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());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 |
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"));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"));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.
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.
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:
- Proactive -- Refreshes the DC JWT before expiration (5-minute buffer) and when it exceeds a maximum age (15 minutes by default)
- Reactive -- On gRPC auth errors (UNAUTHENTICATED), refreshes and retries once
# 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.keycertificate.crt-- Upload to Salesforce External Client Appprivate.key-- Use in your application (keep secure)
- Setup > External Client App Manager > New External Client App
- Flow Type: App acts on behalf of a user
- 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
- Access the identity URL service (
- Callback URL:
https://localhost:1717/OauthRedirect - Enable JWT Bearer Flow and upload
certificate.crt
- External Client App Manager > Your app > Policies tab
- Set Permitted Users to Admin approved users are pre-authorized
- Manage Profiles > Add profiles that should have access
- External Client App Manager > Your app > OAuth Settings
- Click Manage Consumer Details (verify identity)
- Copy the Consumer Key (this is the
client_id)
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" # optionalcargo run -p hyperdb-api-salesforce --example salesforce_auth_example| 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).
| 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 |
Apache-2.0 — see LICENSE-APACHE.