Signing Admin API requests
Rafiki requires every request to the Backend Admin API to include a valid cryptographic signature.
This signature authenticates the caller and ensures the request body has not been altered in transit.
Each request is HMAC‑signed with SHA‑256 using the tenant’s or operator’s API secret. This signature ensures that Rafiki can verify where the request originated and confirm that the payload data matches what was originally signed.
All signed requests include two headers: the signature header and the tenant-Id header.
The signature header authenticates the request payload. Rafiki uses this value to verify the request’s integrity.
signature: t=<timestamp>, v<version>=<digest>t=<timestamp>: The UNIX timestamp (in seconds) when the signature was generated.v<version>=<digest>: The versioned HMAC SHA-256 signature digest. The default version is v1.
tenant-id: <OPERATOR_TENANT_ID><OPERATOR_TENANT_ID>: The unique UUID v4 identifying the tenant or operator.
Rafiki uses the signature to authenticate the tenant or operator and prevent replay attacks.
To protect the Admin API from unauthorized or replayed requests, each client request must include a digital signature that Rafiki can verify. By generating a signature before sending your request, you allow Rafiki to confirm who sent it and ensure the payload matches what was originally signed.
Follow these steps to create a valid signature.
Each signature includes a timestamp representing when it was created. Rafiki uses this value to confirm the request is recent and reject requests outside the configured TTL window (30 seconds by default).
In JavaScript, generate it with Date.now().
To ensure you and Rafiki sign the same data, serialize the GraphQL request consistently.
Use a canonicalization method that orders keys predictably (for example, json‑canonicalize ) applied to the query, variables, and operationName fields.
Combine the timestamp and canonicalized request body with a period (.). This string forms the message to be signed.
<timestamp>.<canonicalized_request_body>Generate the digest using the tenant’s or operator’s API secret as the key and the payload string as the message. The output should be a hexadecimal string.
Include the generated values in your request headers:
signature: t=<timestamp>, v<version>=<digest>tenant-id: <OPERATOR_TENANT_ID>The version number (v1 by default) corresponds to the configured ADMIN_API_SIGNATURE_VERSION environment variable.
Rafiki reconstructs the same payload internally and validates the digest, timestamp, and tenant ID before processing the request.
Below is an example in JavaScript to sign an Admin API request:
const timestamp = Date.now()const version = process.env.ADMIN_API_SIGNATURE_VERSION
const { query, variables, operationName } = requestconst formattedRequest = { variables, operationName, query: print(query)}
const payload = `${timestamp}.${canonicalize(formattedRequest)}`const hmac = createHmac('sha256', process.env.ADMIN_API_SECRET)hmac.update(payload)const digest = hmac.digest('hex')
headers['signature'] = `t=${timestamp}, v${version}=${digest}`headers['tenant-id'] = process.env.OPERATOR_TENANT_ID| Environment variable | Description | Default |
|---|---|---|
ADMIN_API_SIGNATURE_VERSION | The version of the HMAC SHA-256 request-signing algorithm used by the Backend Admin API. | 1 |
ADMIN_API_SECRET | Operator API secret used to sign Backend Admin API requests (HMAC SHA‑256). Set to a strong, random value. Synced to the operator tenant on startup. | — |
OPERATOR_TENANT_ID | The unique identifier of the operator. Must be a UUID v4 generated by the operator. | — |
When Rafiki receives a signed Admin API request, it automatically rebuilds the same payload and verifies the HMAC digest against the operator’s configured secret.
The request is accepted only if the following conditions are met:
- The signature digest matches
- The timestamp is within the allowed TTL window
- The tenant ID is recognized
If any check fails, Rafiki rejects the request before executing any GraphQL operation.
For details on how Rafiki validates incoming requests from its own services, see Verify webhook signatures