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

# SPIFFE

> Call Reducto from SPIFFE workloads with a JWT-SVID from SPIRE or another SPIFFE provider instead of a stored API key.

A SPIFFE provider such as SPIRE gives each workload a SPIFFE ID and can issue it a JWT-SVID, a signed JWT whose `sub` is the SPIFFE ID. Reducto verifies the JWT-SVID and returns an access token. For Idira Secure Workload Access, see the [Idira guide](https://docs.reducto.ai/security/workload-identity-federation/idira.md).

Reducto accepts JWT-SVIDs only. X.509-SVIDs are not supported.

## Requirements for the JWT-SVID

The SPIFFE specification requires only `sub`, `aud` and `exp`. Reducto also needs:

* An `iss` claim with an HTTPS issuer URL.
* An `iat` claim.
* A `kid` header that names a key in the issuer's JWKS.

The issuer URL is not the trust domain. In SPIRE, set the same `jwt_issuer` on the server and on the [OIDC Discovery Provider](https://github.com/spiffe/spire/tree/main/support/oidc-discovery-provider):

```hcl theme={null}
# SPIRE Server
server {
  trust_domain = "acme.com"
  jwt_issuer   = "https://spire-oidc.acme.com"
}
```

```hcl theme={null}
# SPIRE OIDC Discovery Provider
domains    = ["spire-oidc.acme.com"]
jwt_issuer = "https://spire-oidc.acme.com"
```

If the discovery provider is not reachable from the internet, upload the JWKS to Reducto instead.

## Get and inspect a token

From the workload, fetch a JWT-SVID with the Reducto audience:

```bash theme={null}
SUBJECT_TOKEN=$(spire-agent api fetch jwt \
  -socketPath /run/spire/sockets/agent.sock \
  -audience "https://platform.reducto.ai" | sed -n '2p' | tr -d '[:space:]')
```

```json theme={null}
{
  "alg": "ES256",
  "kid": "Xv3k9pQ2mN7tR4wY8zB1cD6f",
  "typ": "JWT"
}
```

```json theme={null}
{
  "iss": "https://spire-oidc.acme.com",
  "aud": ["https://platform.reducto.ai"],
  "sub": "spiffe://acme.com/ns/payments/sa/invoice-parser",
  "iat": 1793322245,
  "exp": 1793322545
}
```

## Configure Reducto

In [Studio](https://studio.reducto.ai), open **Settings > Workload identity** and create a provider with the **SPIFFE** preset:

| Field | Value |
| - | - |
| **Name** | `spiffe-prod` |
| **Issuer** | `https://spire-oidc.acme.com` |
| **Audience** | `https://platform.reducto.ai` |
| **Key source** | OIDC discovery, or **Uploaded JWKS** if the discovery provider is private |

Add a mapping for the SPIFFE ID:

| Key | Value |
| - | - |
| `sub` | `spiffe://acme.com/ns/payments/sa/invoice-parser` |

To allow every workload under a path, use a trailing wildcard, for example `spiffe://acme.com/ns/payments/*`.

## Call Reducto

The simplest setup writes the JWT-SVID to a file and refreshes it, for example with [spiffe-helper](https://github.com/spiffe/spiffe-helper):

```hcl theme={null}
agent_address = "/run/spire/sockets/agent.sock"
cert_dir      = "/var/run/spiffe"
daemon_mode   = true
jwt_svids = [{
  jwt_audience       = "https://platform.reducto.ai"
  jwt_svid_file_name = "reducto.jwt"
}]
```

Then pass a token provider that reads the file. The SDK reads it on every exchange. Instead of code, you can set `REDUCTO_IDENTITY_TOKEN_FILE=/var/run/spiffe/reducto.jwt` next to `REDUCTO_IDENTITY_PROVIDER_ID` and create the client with no arguments.

```python Python theme={null}
import os

from reducto import Reducto
from reducto.auth import identity_token_file

client = Reducto(
    workload_identity={
        "identity_provider_id": os.environ["REDUCTO_IDENTITY_PROVIDER_ID"],
        "provider": identity_token_file("/var/run/spiffe/reducto.jwt"),
    }
)

result = client.parse.run(input="https://cdn.reducto.ai/samples/fidelity-example.pdf")
print(result.result.chunks[0].content[:500])
```

```javascript Node.js theme={null}
import Reducto from "reductoai";
import { identityTokenFile } from "reductoai/auth";

const client = new Reducto({
  workloadIdentity: {
    identityProviderId: process.env.REDUCTO_IDENTITY_PROVIDER_ID,
    provider: identityTokenFile("/var/run/spiffe/reducto.jwt"),
  },
});

const result = await client.parse.run({
  input: "https://cdn.reducto.ai/samples/fidelity-example.pdf",
});
console.log(result.result.chunks[0].content.slice(0, 500));
```

```bash cURL theme={null}
ACCESS_TOKEN=$(jq -n \
  --arg subject_token "$(cat /var/run/spiffe/reducto.jwt)" \
  --arg provider_id "$REDUCTO_IDENTITY_PROVIDER_ID" \
  '{
    grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
    subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
    subject_token: $subject_token,
    identity_provider_id: $provider_id
  }' \
  | curl -sS --fail-with-body -X POST "https://platform.reducto.ai/oauth/token" \
      -H "Content-Type: application/json" -d @- \
  | jq -r .access_token)

curl -X POST "https://platform.reducto.ai/parse" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"input":"https://cdn.reducto.ai/samples/fidelity-example.pdf"}'
```

If your language has a SPIFFE Workload API client, you can pass a custom provider that fetches the JWT-SVID in `get_token` (`getToken` in Node.js) instead of reading a file.

## SPIFFE best practices

* Request JWT-SVIDs with one audience, the Reducto audience, and no others.
* Match exact SPIFFE IDs. Use wildcards only for paths where every workload should have the same access.
* Keep JWT-SVID lifetimes short. The Reducto access token never outlives the JWT-SVID.
* During signing key rotation, publish both keys through discovery, or upload both keys before you sign with the new one.
* Keep server and workload clocks in sync. Reducto allows 30 seconds of skew.
* Protect the Workload API socket. Any process that can fetch the JWT-SVID can exchange it.
