Skip to main content
For deployments that prefer not to manage static API keys, the Go SDK can authenticate via Workload Identity Federation (WIF). The SDK fetches a workload OIDC token from the running environment, exchanges it for a Nexus session JWT, and uses Authorization: Bearer <jwt> on every subsequent API call. WIF is opt-in. When WIFConfig.Enabled == false (the zero value), the SDK falls back to the static APIKey flow and behaves exactly as in v0.1.0.

Enabling

APIKey becomes optional when WIF is enabled. If you provide both, the SDK uses WIF and treats APIKey as a fallback if a refresh fails after the initial connect.
Security note (v0.9.0): BaseURL must use https:// - NewClient refuses plain-http endpoints (loopback/localhost excepted for development), since credentials travel on every request.

Supported providers

The SDK probes the environment in this order when Provider is WIFProviderAuto: The OIDC providers are stdlib-only; aws_iam is the sole feature that uses the aws-sdk-go-v2 config/signer dependency. You can also pin a specific provider:

AWS IAM (non-EKS AWS compute)

ECS/Fargate tasks, Lambda functions, and plain EC2 instances have IAM credentials but no OIDC token, so the OIDC providers above cannot serve them. The aws_iam provider closes this gap:
How it works:
  1. The SDK SigV4-signs an STS GetCallerIdentity request using the standard AWS credential chain (task role, instance profile, env). The request is never sent to AWS by the SDK.
  2. The signed request (headers + body) is posted to /v1/auth/token-exchange as {"provider":"aws_iam","aws_sts_request":...}.
  3. Nexus replays it against a pinned STS endpoint; AWS answers with the caller’s IAM identity, which Nexus matches against an aws_iam trust policy (subject = the assumed-role ARN, normalized to arn:aws:iam::<account>:role/<name>).
Nothing else to configure: the SDK signs your service’s own host (the BaseURL host) into the X-Nexus-Server-ID header inside the SigV4 signature, and Nexus verifies it against the host the exchange request actually arrived on. A captured signed request is therefore valid for that ONE service only - it cannot be replayed against any other service or deployment. aws_iam is never auto-detected - select it explicitly. Resolvable AWS credentials alone (e.g. a developer laptop’s ~/.aws/credentials) are too weak a signal to silently authenticate with.

Custom TokenSource

Provide a TokenSourceFunc to bypass auto-detection entirely. This is the right hook for tests, custom CI flows, or topologies the SDK doesn’t natively recognise.
When TokenSource is non-nil it takes precedence over Provider.

Audience

GCP and Azure require an audience claim when issuing the OIDC token. Set it via WIFConfig.Audience:
The default is "westyx-nexus" if unset. Kubernetes service-account tokens carry the audience configured in the pod’s projected token spec - the field is informational there. Azure IMDS is the exception: the generic default is refused with a clear error, because Azure AD rejects it as a resource. On the IMDS path (plain VM / App Service - no federated token file) you MUST set Audience to your app registration’s Application ID URI:
On AKS with Azure Workload Identity the projected federated token file ($AZURE_FEDERATED_TOKEN_FILE) is preferred automatically and no Audience is needed - the file’s audience is set by the cluster.

Token exchange flow

Internally, the SDK runs this protocol:
  1. OIDC token fetch - WIFConfig.TokenSource(ctx) (or the auto-detected built-in equivalent) returns a workload-identity JWT. (For aws_iam this step signs an STS GetCallerIdentity request instead - there is no OIDC token.)
  2. Token exchange - the SDK POSTs {"oidc_token": "<jwt>"} (or {"provider":"aws_iam","aws_sts_request":...}) to /v1/auth/token-exchange. The backend validates the credential, then returns:
  3. Bearer auth on subsequent calls - every /v1/* request carries Authorization: Bearer <session_token>.
  4. Pre-emptive refresh - when a request finds the session expires within 60 s the SDK refreshes (re-running steps 1-2) before issuing the call. Concurrent callers see exactly one in-flight exchange.

SSE auth_expiring event

About 5 minutes before the session JWT expires, the backend emits an auth_expiring SSE control event. The SDK reacts by triggering a non-blocking session refresh - it does NOT call /sync, and the event is NOT surfaced through the StreamObserver. When the server force-closes the stream at expiry, the SDK reconnects with the freshly-issued bearer.

Errors

Use errors.Is for sentinel matching:

Service kind awareness

The token-exchange response includes service_kind ("frontend" or "backend"). The SDK stores this and exposes it via client.Kind(). If the resolved kind is "frontend", GetSecret(...) returns ErrServiceKindMismatch regardless of the key type - frontend services are public by design and must not hold secrets.

Project-level vs service-level trust policies

The Nexus backend supports trust policies at two scopes:

How it works

The SDK always connects to a specific service via its baseUrl (e.g. https://worker.westyx.dev). This is true regardless of whether you use WIF or a static API key - the URL slug identifies the service, and the backend resolves which configs, secrets, and flags to serve based on it. At token exchange time the backend applies this resolution order:
  1. Does a service-level trust policy match this workload’s OIDC token claims? - if yes, authorise.
  2. Does a project-level trust policy match? - if yes, authorise.
  3. Neither matches - return 401.
This means you can configure one project-level trust policy and all services in the project will accept that workload’s tokens. Each service SDK instance still uses its own baseUrl, so each gets its own service’s data.

Example: one cloud identity for 50 services

All three services share the same Kubernetes service account. No separate cloud identities required.

Security trade-off

A project-level trust policy is less restrictive than a service-level one. Any workload that satisfies the policy can connect to any service in the project by targeting its baseUrl. If a pod is compromised, an attacker could read configs and secrets of sibling services.For services handling sensitive data (payments, credentials, PII) consider a dedicated service-level trust policy instead.
The Nexus UI surfaces this trade-off when configuring trust policies on the Security tab of a project.