# Client certificates (mTLS)

> For a partner server that presents a certificate. Two ways to verify it, either or both:

Guide - Since version 0.1.0 - Flexway v1.6.3 - https://flexway.io/en/docs/operations-client-certificates/

> Operations guide: configure, deploy and observe the gateway. What the capability does, product side: [product guide](https://flexway.io/en/docs/product-overview/); calling it: [developer guide](https://flexway.io/en/docs/developer-overview/).

For a **partner server** that presents a certificate. Two ways to verify it, either or both:

| Where the certificate is verified | Configuration | When |
| --- | --- | --- |
| **The edge** (Envoy, an ingress) terminates mTLS and vouches for it in `x-forwarded-client-cert` | `server.trusted_proxies` = the edge's range | The usual case in a cluster |
| **The gateway itself**, on its TLS listener | `server.tls` (`cert_file`, `key_file`, `client_ca_file`) | No edge terminating TLS |

Each certificate is **declared** - by its URI SAN (preferred: it survives a renewal) or by its
subject - and bound to a tenant and to operations:

```yaml
server:
  trusted_proxies: [10.0.0.0/8]      # the edge, and only it: from anyone else the header is ignored
auth:
  mtls:
    clients:
      - { id: bank-settler, uri: "spiffe://bank.example/settler", tenant: bank, operations: [settle] }
      - { id: legacy-batch, subject: "CN=batch,O=Acme", tenant: acme, operations: [getSettlement] }
```

The gateway's own TLS listener, when there is no edge (port `FLEXWAY_TLS_ADDR`, `:8443` by default,
next to `:8080`):

```yaml
server:
  tls:
    cert_file: /etc/flexway/tls/tls.crt          # reloaded when it rotates, no restart
    key_file: /etc/flexway/tls/tls.key
    client_ca_file: /etc/flexway/tls/clients-ca.pem   # the client certificates' authority, reloaded too
```

The contract declares the `mutualTLS` alternative:

```yaml
components:
  securitySchemes:
    partner: { type: mutualTLS }
paths:
  /settlements:
    post:
      operationId: settle
      security: [ { partner: [] } ]    # a declared certificate, nothing else
```

**Check**:

| Call | Response |
| --- | --- |
| A declared certificate, a bound operation | relayed; the certificate's `X-Tenant`; `x-forwarded-client-cert` **never** reaches the upstream |
| A declared certificate, an operation it does not name | `403` |
| A valid but undeclared certificate, or none | `401` "requires a client certificate" |
| An `x-forwarded-client-cert` header from anywhere but the edge | ignored - `401` |
| A certificate from another authority, on the TLS listener | the handshake fails |
| A token on an operation that accepts `mutualTLS` only | `401`; the token is not even verified |

> **Reading order**: a certificate verified by the listener, then the edge's
> `x-forwarded-client-cert`, then an API key, then a token. The **contract** decides which mechanism
> satisfies which alternative: a token never stands in for a key, nor the reverse. The MCP surface
> accepts OAuth tokens only ([Agents and MCP](https://flexway.io/en/docs/operations-agents-mcp/)). `server.tls` is not reloadable: adding or removing it needs a
> restart; its **files** are re-read on their own.
