# Request signing model

Every state-changing request (and some read requests) carries four HTTP headers that prove identity, bind the request to a moment in time, and prevent replay attacks.

## The four authentication headers

| Header | Format | Purpose |
|---|---|---|
| `authorization` | `ApiKey <uuid>` or `Bearer <jwt>` | Identifies the caller |
| `x-request-nonce` | 16 hex chars (8 random bytes) | Single-use replay protection |
| `x-request-timestamp` | 16 hex chars (u64 LE, milliseconds since Unix epoch) | Clock-skew protection |
| `x-request-assertion` | Base64url, no padding (RFC 4648 section 5) | Proves the request was not tampered with |

### `authorization`

Machine users and agents send `ApiKey <uuid>`, where the UUID is the API key returned when the machine user was created (or last rotated). Human users send `Bearer <jwt>`, where the JWT comes from the cloud identity provider session.

### `x-request-nonce`

Sixteen lowercase hex characters encoding 8 random bytes. Generate this fresh for every request. The vault rejects any nonce it has seen before, so reusing a nonce causes the request to fail even if everything else is correct.

Example: `a3f80c1d4e7b9025`

### `x-request-timestamp`

Sixteen lowercase hex characters encoding an unsigned 64-bit integer in **little-endian** byte order. The integer is the current Unix time in milliseconds.

The vault rejects requests whose timestamp is more than 5 minutes away from server time in either direction. Keep your system clock synchronized (NTP) and encode correctly: little-endian, not big-endian.

Example: `0068e5cf8b010000` represents 1 700 000 000 000 ms.

### `x-request-assertion`

The shape of this header depends on the caller type:

* **Machine users and agents:** 64 raw bytes of an Ed25519 signature, base64url-encoded without padding. Always 86 characters. See [Machine Users and Agents](/develop/authentication/machine-users) for the full transcript construction.

* **Human users:** UTF-8 JSON of a WebAuthn assertion response (credential ID, authenticator data, client data JSON, and signature), base64url-encoded. Typically 200-500 characters.

## Which endpoints require all four headers

Most endpoints require all four headers. The following read-only endpoints accept only `authorization` and skip the nonce, timestamp, and assertion:

* `GET /v1/admin-policies`: [Admin Policies reference](/api-reference#tag/adminpolicies)
* `GET /v1/audit-logs`: [Audit Logs reference](/api-reference#tag/auditlogs)
* `GET /v1/whoami`: [Whoami reference](/api-reference#tag/whoami)
* `GET /v1/requests/{request_id}`: [Requests reference](/api-reference#tag/requests)

The info endpoints (`GET /v1/info`, `GET /v1/system-info`), the health endpoint (`GET /health`), and the spec endpoints (`/openapi.json`, `/openapi.yaml`) require no authentication at all (see [Common](/api-reference#tag/common) and [Docs](/api-reference#tag/docs)).

## Why this design

The API key establishes identity. The nonce and timestamp together prevent replay attacks: an attacker who captures a valid request cannot reuse it because the nonce is single-use and the timestamp window is narrow. The Ed25519 assertion binds every byte of the request to the caller's private key, so a man-in-the-middle cannot alter the method, path, query, or body without invalidating the signature.

The vault's signature scheme uses a SHA-256 transcript of the request rather than signing the raw bytes directly. This structured approach means the signature covers all security-relevant fields in a well-defined order, making the verification logic straightforward to audit.
