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

# Kotlin SDK - Workload Identity Federation

> WIF providers, token exchange, and trust policies for the Kotlin SDK.

Workload Identity Federation (WIF) lets your JVM application authenticate to Nexus using the cloud-provider identity of the workload itself - no static API keys to store or rotate.

## How it works

1. The SDK obtains an OIDC token from the local cloud metadata endpoint (or a custom `tokenSource`).
2. It exchanges the token at `POST /v1/auth/token-exchange` for a short-lived Nexus session token.
3. All subsequent HTTP requests use `Authorization: Bearer <session_token>` instead of `X-Nexus-API-Key`.
4. When the server sends an `auth_expiring` SSE event, the SDK proactively refreshes the session token before it expires.

## Enabling WIF

```kotlin theme={null}
val config = NexusConfig(
    baseUrl = "https://my-service.westyx.dev",
    wif = WifConfig(
        enabled  = true,
        provider = "auto",      // or specify: kubernetes / aws / gcp / azure / developer
        audience = "westyx-nexus",
    ),
)
val client = NexusClient.create(config)
```

Leave `apiKey = null` when using WIF. If both are set, the session token takes precedence once exchanged.

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

## Providers

### `kubernetes`

Reads the service account token projected into every Kubernetes pod.

* **Token path:** `/var/run/secrets/kubernetes.io/serviceaccount/token`
* **Setup:** Mount the service account token (default in Kubernetes 1.21+). Configure a trust policy in Nexus with the cluster's OIDC issuer URL.

```kotlin theme={null}
WifConfig(enabled = true, provider = "kubernetes")
```

### `aws`

Reads the web identity token file used by IAM Roles for Service Accounts (IRSA) on EKS.

* **Environment variable:** `AWS_WEB_IDENTITY_TOKEN_FILE` must point to an existing token file.

```kotlin theme={null}
WifConfig(enabled = true, provider = "aws")
```

### `aws_iam` *(v0.9.0)*

For non-EKS AWS compute (ECS/Fargate, Lambda, plain EC2) that has IAM credentials but no OIDC token. The SDK SigV4-signs an STS `GetCallerIdentity` request with the standard AWS credential chain - **never sending it to AWS** - and posts the signed headers + body to `/v1/auth/token-exchange`. Nexus replays it against a pinned STS endpoint and matches the caller's IAM role against an `aws_iam` trust policy (subject = `arn:aws:iam::<account>:role/<name>`). Requires the optional `software.amazon.awssdk:auth` + `:regions` dependencies; not auto-detected.

```kotlin theme={null}
WifConfig(
    enabled   = true,
    provider  = "aws_iam",
    awsRegion = "eu-central-1",   // optional - defaults to the AWS region chain + EC2 IMDS
)
```

The signed `X-Nexus-Server-ID` is your service's own base-URL host, verified by the backend against the host the request arrived on - so a captured signed request is valid for that ONE service only.

### `gcp`

Fetches a signed OIDC ID token from the GCP instance metadata endpoint.

* **Metadata URL:** `http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=<audience>`

```kotlin theme={null}
WifConfig(
    enabled  = true,
    provider = "gcp",
    audience = "westyx-nexus",   // must match trust policy
)
```

### `azure`

Authenticates an Azure managed identity or AKS workload identity. On AKS with Azure Workload Identity the SDK prefers the projected federated token file (`$AZURE_FEDERATED_TOKEN_FILE`); otherwise it fetches a managed-identity token from the Azure IMDS.

* **Federated file (preferred):** `$AZURE_FEDERATED_TOKEN_FILE` (AKS Workload Identity).
* **IMDS URL:** `http://169.254.169.254/metadata/identity/oauth2/token`

<Warning>
  **v0.9.0 - audience is required on the IMDS path.** Earlier versions requested a privileged `management.azure.com` ARM token; the SDK now requests your app registration's Application ID URI and **refuses** the generic default (`westyx-nexus`), because Azure AD rejects it as a resource. Set `audience` to `api://<client-id>`. On the federated-file path no `audience` is needed.
</Warning>

```kotlin theme={null}
WifConfig(
    enabled  = true,
    provider = "azure",
    audience = "api://<client-id>",   // required on the IMDS path
)
```

### `developer`

Reads an OIDC token from the environment variable `NEXUS_DEV_OIDC_TOKEN`. Intended for local development and CI pipelines where a real cloud identity is not available.

```kotlin theme={null}
WifConfig(enabled = true, provider = "developer")
```

```bash theme={null}
export NEXUS_DEV_OIDC_TOKEN="$(some-oidc-token-generator)"
```

### `auto` (default)

Probes the environment and returns the first provider whose credential material is actually present *(v0.9.0 - the token files are stat-ed and the metadata servers live-probed, \~1 s timeout; earlier versions keyed on environment-variable presence)*:

1. `kubernetes` (token file exists)
2. `aws` (`AWS_WEB_IDENTITY_TOKEN_FILE` set and the file exists)
3. `azure` (`AZURE_FEDERATED_TOKEN_FILE` set and the file exists)
4. `gcp` (metadata server reachable)
5. `azure` IMDS (metadata server reachable)
6. `developer` (`NEXUS_DEV_OIDC_TOKEN` set)

`aws_iam` is deliberately **not** in the auto-detect chain - select it explicitly.

```kotlin theme={null}
WifConfig(enabled = true)   // provider defaults to "auto"
```

## Custom token source

For environments not covered by the built-in providers, supply an async lambda via `tokenSource`. When `tokenSource` is set, `provider` is ignored entirely.

```kotlin theme={null}
WifConfig(
    enabled = true,
    tokenSource = {
        // any suspend function that returns an OIDC JWT string or null
        myVaultClient.getOidcToken("westyx-nexus")
    },
)
```

Return `null` to signal that no token is available - the SDK will throw `NexusWifNotConfiguredException`.

## Token exchange flow

At client creation, the SDK calls:

```
POST /v1/auth/token-exchange
Content-Type: application/json

{"oidc_token": "<provider-oidc-token>"}
```

The server validates the OIDC token against the trust policy and returns:

```json theme={null}
{
  "session_token": "...",
  "expires_in": 3600,
  "service_kind": "backend",
  "wif_provider": "kubernetes"
}
```

The SDK stores `session_token` and sets `sessionExpiry = now + expires_in - 60s` (60-second safety margin). All subsequent requests use `Authorization: Bearer <session_token>`.

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

The SDK always connects to a specific service via its `baseUrl`. The URL slug identifies the service - this works the same as API keys. At token exchange time, the backend checks service-level trust policies first, then falls back to project-level.

<Warning>
  A project-level trust policy is less restrictive than a service-level one. Any workload satisfying the policy can connect to any service in the project by targeting its `baseUrl`. For services handling sensitive data (payments, PII) consider a dedicated service-level trust policy.
</Warning>

## Error handling

| Exception                              | Cause                                                                                                 |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `NexusWifNotConfiguredException`       | No token could be resolved (token file missing, env var not set, metadata unreachable)                |
| `NexusWifTokenExchangeFailedException` | Token obtained but exchange endpoint rejected it (trust policy mismatch, expired token, server error) |
| `NexusUnauthorizedException`           | Session token expired and silent refresh failed                                                       |

```kotlin theme={null}
val client = try {
    NexusClient.create(config)
} catch (e: NexusWifNotConfiguredException) {
    logger.error("WIF token not available: ${e.message}")
    exitProcess(1)
} catch (e: NexusWifTokenExchangeFailedException) {
    logger.error("WIF exchange failed: ${e.message}")
    exitProcess(1)
}
```
