Skip to main content
Workload Identity Federation (WIF) lets the SDK authenticate without a static API key: it exchanges a workload credential for a short-lived Nexus session JWT.

Enabling WIF

endpoint must use https:// - create rejects plain-http endpoints (loopback excepted for development), since the API key, OIDC token and session JWT travel on every request.

Provider auto-detection

With credential: WifProvider::Auto (the default) the SDK probes the environment in order and picks the first provider whose credential material is actually present. Token files are stat-ed; the metadata servers are live-probed with a 2 s deadline: Every free filesystem and environment check comes before every network probe, and developer is always last - so on real cloud compute a real workload credential always wins over a credential left on a laptop. If none match, create returns WifNotConfigured. aws_iam is not auto-detected: resolvable AWS credentials alone (a developer laptop’s ~/.aws/credentials, say) are too weak a signal.

Token sources

File reads happen on the SDK’s blocking pool, so a stalled filesystem cannot block the runtime.

Session lifecycle

  • Refreshed pre-emptively, about 60 s before expiry.
  • The SSE auth_expiring event forces a refresh. The backend sends it about five minutes ahead, so a non-forced refresh would decline to act.
  • A 401 forces one refresh and retries the request once, so a client whose local clock disagrees with the server re-authenticates instead of repeating a rejected token.
  • Concurrent refreshes are de-duplicated - callers share one in-flight exchange.
  • A failed refresh fails closed: never a stale bearer, never an empty API-key header. A static API key is used as a fallback only if you explicitly configured one alongside WIF.
  • expires_in is clamped - a missing, zero or negative value is floored so a fresh session is not immediately “expiring”, and an absurd value is capped at 24 h.

Developer credentials

westyx dev setup obtains a Nexus session token for a service and writes it to dev-credentials.json in your OS config directory - $XDG_CONFIG_HOME/westyx on Linux, ~/Library/Application Support/westyx on macOS, %AppData%\\westyx on Windows - at mode 0600 inside a 0700 directory. With WIF enabled and no cloud identity present, the SDK picks that token up and sends it as the bearer:
  • Matched on the endpoint host and non-default port. Scheme and a trailing slash are ignored; the host identifies the service and a non-default port distinguishes two on the same host (a default :443 is normalised away, so writing it or omitting it matches the same entry).
  • No token exchange happens. The CLI already performed it, so the session token goes straight through. No Keycloak refresh token is read, stored or sent.
  • Probed last. A live GCE metadata server or an AKS projected token always wins, so the same binary on real cloud compute uses the real workload identity.
  • Re-read on every refresh, so running westyx dev setup again mid-run takes effect without a restart.
  • An expired entry is refused with a message naming westyx dev setup --service=<name> - never sent, never silently skipped.
  • A file readable by other local users is refused, with a message naming the chmod that fixes it.
WESTYX_DEV_TOKEN takes precedence over the file when set. It carries no expiry, so an eight-hour lifetime is assumed; a 401 still forces one refresh and retry. Select it explicitly with credential: WifProvider::Developer.into().

Supplying the credential yourself

Implement WifCredentialSource for a credential the SDK does not know about - a sidecar, a secret manager, a test double:
The trait is async and dyn-compatible. Its defaulted exchange_payload wraps token() in {"oidc_token": ...}; override it to produce the entire exchange body, which is what a credential that is not an OIDC token needs - a signed request, a platform attestation. In that case token() is never called. A source and a provider cannot both be configured: credential is one field, so the combination is unrepresentable. The SDK runs the source on its own task, and a panic inside it is caught, reported through tracing, and turned into a failed refresh - it cannot take a background task down.

Metadata client hardening

Metadata and IMDS calls use a dedicated HTTP client: a 2 s deadline, redirects disabled (so a 3xx from a spoofed metadata endpoint cannot redirect a credential-bearing request), no environment or system proxy, and a 64 KiB response cap.

AWS IAM (non-EKS AWS compute)

Credentials come from the standard AWS chain (task role, instance profile, environment). The SDK SigV4-signs an STS GetCallerIdentity request and never sends it to AWS: it posts the signed headers and body to /v1/auth/token-exchange, and Nexus replays them against a pinned STS endpoint to prove your IAM role. Your service’s own endpoint host and non-default port are signed into X-Nexus-Server-ID, and Nexus verifies it against the host and port the exchange arrived on - so a captured signed request is valid for one service only. Every refresh signs a fresh request, keeping the SigV4 timestamp inside STS’s validity window. Selecting aws_iam without the feature returns an error naming the feature. With the feature on, the AWS crates resolve against the SDK’s declared Rust version floor - see Installation.

Token exchange contract

POST /v1/auth/token-exchange carries the slug Host header (Double-Gate) and no Authorization header. Statuses map to distinct errors - 400 to BadRequest, 401 to Unauthorized, 403 to Forbidden, 402 to Billing - and no response body is ever embedded in an error message, since it may echo credential material.

Configuration errors caught at construction

Two contradictions are rejected by create rather than surfacing later as a failed refresh, each naming the fix:
  • aws_region set with any credential other than WifProvider::AwsIam. The region is used only by that provider, which is never auto-detected - so setting it under Auto did nothing at all.
  • WifProvider::AwsIam selected without the aws-iam cargo feature.