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

# Node.js SDK - Workload Identity Federation

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

For deployments that prefer not to manage static API keys, the Node.js 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 `undefined` (the default), the SDK falls back to the static `apiKey` flow and behaves exactly as in v0.1.0.

## Enabling

```ts theme={null}
import { NexusClient, WIFProviderAuto } from '@westyx-nexus/sdk-nodejs';

const client = await NexusClient.create({
  endpoint: 'https://blue-ocean-a5rx7.westyx.dev',
  wif: {
    enabled:  true,
    provider: WIFProviderAuto, // default - auto-detect from env
  },
});
```

`apiKey` becomes optional when WIF is enabled.

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

## Supported providers

When `provider` is `WIFProviderAuto` 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                                                                                                                                                                                        |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WIFProviderKubernetes`                | `/var/run/secrets/kubernetes.io/serviceaccount/token` exists                                                                                  | reads the projected service-account token file (rotated by kubelet)                                                                                                                               |
| `WIFProviderAWS`                       | `AWS_WEB_IDENTITY_TOKEN_FILE` set **and the file exists** (EKS IRSA)                                                                          | reads the IRSA token file at that path                                                                                                                                                            |
| `WIFProviderAzure` (Workload Identity) | `AZURE_FEDERATED_TOKEN_FILE` set and the file exists (AKS)                                                                                    | reads the projected federated token file                                                                                                                                                          |
| `WIFProviderGCP`                       | `metadata.google.internal` reachable (live probe)                                                                                             | GETs `metadata.google.internal/computeMetadata/v1/.../identity?audience=<aud>` with `Metadata-Flavor: Google`                                                                                     |
| `WIFProviderAzure` (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) |
| `WIFProviderAWSIAM`                    | **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 are stdlib-only; `aws_iam` is the sole feature that pulls in the AWS signer/credential packages (`@smithy/signature-v4`, `@aws-sdk/credential-provider-node`).

You can also pin a specific provider:

```ts theme={null}
import { WIFProviderKubernetes } from '@westyx-nexus/sdk-nodejs';

wif: { enabled: true, provider: WIFProviderKubernetes }
```

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

```ts theme={null}
import { WIFProviderAWSIAM } from '@westyx-nexus/sdk-nodejs';

wif: {
  enabled:  true,
  provider: WIFProviderAWSIAM,
  // 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 `endpoint` 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 token source

Provide a `tokenSource` function to bypass auto-detection. The right hook for tests, custom CI flows, or topologies the SDK doesn't natively recognise.

```ts theme={null}
wif: {
  enabled: true,
  tokenSource: async () => readMyOidcToken(),
}
```

When `tokenSource` is set it takes precedence over `provider`.

## Audience

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

```ts theme={null}
wif: {
  enabled:  true,
  provider: WIFProviderGCP,
  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** - the configured token source 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. **Session storage** - the SDK caches the session token in memory and uses `Authorization: Bearer <session>` on all subsequent `/sync` and `/stream` requests.
4. **Auto-refresh** - \~60 s before expiry the SDK silently re-runs steps 1-2 from a background promise. 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.

Failures throw `NexusWIFTokenExchangeFailedError` (or `NexusWIFNotConfiguredError` for environment-detection failures). On the initial connect these are wrapped in `NexusInitError`. Background-refresh failures are logged via `console.error` and the existing session is retried.

## Service kind

The token-exchange response includes a `service_kind` field (`"backend"` or `"frontend"`). The SDK propagates this to the `NexusClient.kind` getter, which `getSecret()` uses to gate access:

```ts theme={null}
if (client.kind === 'frontend') {
  // throws NexusServiceKindMismatchError - frontends must not hold secrets
  client.getSecret('DB_PASSWORD');
}
```

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

At token exchange time the backend checks service-level policies first, then falls back to project-level. All services in a project can share one Kubernetes service account.

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