> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# .NET SDK - Workload Identity Federation

> Bearer-JWT auth with Kubernetes, AWS, GCP, and Azure for the .NET SDK.

For deployments that prefer not to manage static API keys, the .NET 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 `NexusConfig.Wif` is `null` (the default), the SDK falls back to the static `ApiKey` flow and behaves exactly as in v0.1.0.

## Enabling

```csharp theme={null}
await using var client = await NexusClient.CreateAsync(new NexusConfig
{
    BaseUrl = "https://blue-ocean-a5rx7.westyx.dev",
    Wif = new WIFConfig
    {
        Enabled  = true,
        Provider = WIFProvider.Auto, // default - auto-detect from env
    },
});
```

`ApiKey` becomes optional when WIF is enabled.

<Note>
  **Security note (v0.9.0):** `BaseUrl` must use `https://` - `NexusClient.CreateAsync` refuses plain-http endpoints (loopback/localhost excepted for development), since credentials travel on every request.
</Note>

## Supported providers

When `Provider` is `WIFProvider.Auto` the SDK probes the environment in this order and picks the first provider whose credential material is actually present *(v0.9.0 - file stats + live metadata probes, \~1 s timeout; earlier versions keyed on environment variables absent on real cloud nodes)*:

| Provider constant                       | Auto-detect signal                                                                                                                            | Credential                                                                                                                                                                                         |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WIFProvider.Kubernetes`                | `/var/run/secrets/kubernetes.io/serviceaccount/token` exists                                                                                  | reads the projected service-account token file (rotated by kubelet)                                                                                                                                |
| `WIFProvider.AWS`                       | `AWS_WEB_IDENTITY_TOKEN_FILE` set **and the file exists** (EKS IRSA)                                                                          | reads the IRSA token file at that path                                                                                                                                                             |
| `WIFProvider.Azure` (Workload Identity) | `AZURE_FEDERATED_TOKEN_FILE` set and the file exists (AKS)                                                                                    | reads the projected federated token file                                                                                                                                                           |
| `WIFProvider.GCP`                       | `metadata.google.internal` reachable (live probe)                                                                                             | GETs `metadata.google.internal/computeMetadata/v1/.../identity?audience=<aud>` with `Metadata-Flavor: Google`                                                                                      |
| `WIFProvider.Azure` (IMDS)              | `169.254.169.254/metadata` reachable (live probe)                                                                                             | GETs an Azure IMDS managed-identity token - **requires `Audience = "api://<client-id>"`** (your app registration's Application ID URI; the generic default is refused because Azure AD rejects it) |
| `WIFProvider.AWSIAM`                    | **not auto-detected - select explicitly** (resolvable AWS credentials alone, e.g. a dev laptop's `~/.aws/credentials`, are too weak a signal) | SigV4-signs an STS `GetCallerIdentity` request (never sent to AWS) that Nexus replays to prove your IAM role - works on **ECS/Fargate, Lambda, and plain EC2**                                     |

The OIDC providers use only the BCL HTTP client; `aws_iam` additionally uses `AWSSDK.Core` for the credential/region chain.

You can also pin a specific provider:

```csharp theme={null}
Wif = new WIFConfig
{
    Enabled  = true,
    Provider = WIFProvider.Kubernetes,
}
```

## 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:

```csharp theme={null}
Wif = new WIFConfig
{
    Enabled  = true,
    Provider = WIFProvider.AWSIAM,
    // AWSRegion = "eu-central-1", // optional - defaults to the standard AWS region chain (env, config file, EC2 IMDS)
}
```

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.

## Custom `TokenSource`

Provide a `TokenSourceFunc` to bypass auto-detection entirely.

```csharp theme={null}
Wif = new WIFConfig
{
    Enabled = true,
    TokenSource = ct => ReadMyOidcTokenAsync(ct),
}
```

When `TokenSource` is non-null it takes precedence over `Provider`.

## Audience

GCP and Azure require an `audience` claim when issuing the OIDC token:

```csharp theme={null}
Wif = new WIFConfig
{
    Enabled  = true,
    Provider = WIFProvider.GCP,
    Audience = "westyx-nexus",
}
```

The default is `"westyx-nexus"` if unset.

**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 (`api://<client-id>`). On AKS with Azure Workload Identity the projected federated token file (`$AZURE_FEDERATED_TOKEN_FILE`) is preferred automatically and no `Audience` is needed.

## Token exchange flow

1. **OIDC token fetch** - `WIFConfig.TokenSource` (or the auto-detected built-in equivalent) returns a workload-identity JWT.
2. **Token exchange** - the SDK POSTs `{"oidc_token": "<jwt>"}` to `/v1/auth/token-exchange`. The backend validates the OIDC issuer/signature and returns:
   ```json theme={null}
   {
     "session_token":  "<jwt>",
     "token_type":     "Bearer",
     "expires_in":     3600,
     "service_kind":   "backend",
     "wif_provider":   "kubernetes"
   }
   ```
3. **Bearer auth on subsequent calls** - every `/v1/*` request carries `Authorization: Bearer <session_token>`.
4. **Auto-refresh** - \~60 s before expiry the SDK silently re-runs steps 1-2 from a background `Task`. The active SSE stream is not interrupted.
5. **`auth_expiring` event** - when the server emits this control event over SSE, the SDK pre-emptively refreshes the session before the server force-closes the stream.

## Split HTTP clients

The SDK uses **two separate `HttpClient` instances** - one for short requests (`/sync`, `/token-exchange`, `Timeout = 10s`) and one for the long-lived SSE stream (`Timeout = Timeout.InfiniteTimeSpan`). This prevents a consumer-supplied short timeout from killing the stream after a few seconds. If you pass your own `HttpClient` via `NexusConfig.HttpClient`, the SDK clones it for the stream side and overrides the timeout - your sync client is untouched.

## Project-level vs service-level trust policies

The Nexus backend supports trust policies at two scopes:

| Scope             | What it covers                               | Recommended for                                   |
| ----------------- | -------------------------------------------- | ------------------------------------------------- |
| **Project-level** | All services in the project share one policy | Most teams - no per-service cloud identity needed |
| **Service-level** | A specific policy bound to one service only  | High-security services (payments, PII)            |

<Warning>
  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`. For services handling sensitive data (payments, credentials, PII) consider a dedicated service-level trust policy.
</Warning>
