Forward the Caller's JWT to an Upstream
By default AISIX keeps caller authentication and upstream authentication separate: it verifies the caller's credential, then reaches the upstream with its own. That is the right default for a model provider, which has no use for your organization's identities.
It is the wrong default when the upstream is an internal service that authorizes on the end user's claims. In an environment where every hop already carries an organization-wide JWT, inserting a gateway that stops relaying it breaks the chain — the upstream sees the gateway, not the employee who asked.
forward_jwt_header closes that gap. When it is set on an upstream and the caller authenticated with a JWT, AISIX delivers the verified token to that upstream in the header you name, unchanged: no claim is added, removed, or rewritten.
Authentication is untouched. Signature, expiry, audience, scopes and claim mappings all apply exactly as described in JWT Authentication. This setting only decides whether the token continues past the gateway.
Where It Applies
Set the field on the resource that names the upstream:
| Field | Applies to |
|---|---|
provider_key.request.forward_jwt_header | Requests to that provider key's upstream through the standard endpoints (/v1/chat/completions, /v1/messages, /v1/responses, embeddings, rerank, audio, images, and the files/batches/fine-tuning surface) |
passthrough_route.forward_jwt_header | Requests served by that reverse-proxy route |
mcp_server.forward_jwt_header | Tool calls to that MCP server, for both type: mcp and type: openapi — so a REST API exposed as tools receives the token on every tool call |
It is off by default everywhere. An upstream that does not set it receives no caller token, which is the behavior of every upstream before this setting existed.
How the Header Is Chosen
Any header name is accepted, including the ones that normally carry a credential. That is deliberate: delivering the token into the header an internal service already reads is what lets that service stay unchanged when you put a gateway in front of it.
- Naming
authorizationorproxy-authorizationsendsBearer <token>, the form those headers are defined to carry. - Any other header carries the bare token.
- Header names must be lowercase.
Where the header you name is also where AISIX would put its own credential, the caller's token replaces it. The upstream receives the end user's token in place of the gateway's, never both. Point the field at a header of its own if the upstream needs to authenticate the gateway and identify the end user.
A Bedrock-shaped provider key is the exception, because its requests are SigV4-signed and the signer owns authorization along with the x-amz-* and x-amzn-bedrock-accept headers. A token delivered into one of those would break the signature, so the gateway drops it. AISIX Cloud rejects such a slot when you save the provider key; in resources.yaml there is nothing to reject it, so name a header of its own there.
These names are rejected, because a token in them does not identify a sender — it corrupts the exchange:
- Message-framing headers:
host,content-length,content-type,content-encoding,transfer-encoding,connection,keep-alive,te,trailer,upgrade,expect,accept,accept-encoding. anthropic-version, which selects the wire format an Anthropic-shaped upstream answers in.- The MCP protocol headers
mcp-session-id,mcp-protocol-versionandlast-event-id, which an MCP server's protocol layer refuses outright — naming one stops the gateway connecting to that server at all. - The gateway's own
x-aisix-*namespace, whose values are gateway assertions such as thex-aisix-request-idcorrelation id.
When Nothing Is Sent
Three cases send no token, none of them an error:
- The upstream does not set
forward_jwt_header. - The caller authenticated with a caller API key rather than a JWT. There is no verified token to relay, so the header is absent rather than empty — and a value the caller sent under that name is removed, so an upstream can trust that the header, when present, holds an identity AISIX verified.
- On a
/v1/realtimeWebSocket, which builds its upstream handshake separately and does not carry this setting.
Security Notes
Do not enable this toward a public model provider. The setting exists for internal upstreams under your control. Forwarding employee identity tokens to a third-party API exposes your organization's claims to that vendor, to its logs, and to anyone who can read them.
Check what the upstream does with the aud claim. A service that validates the audience will reject a token minted for AISIX. This setting suits an internal service that reads the claims of a token its own identity provider issued; it is not a token-exchange mechanism and does not re-mint anything.
The token is a live credential in transit. AISIX marks the delivered header value sensitive, so the gateway redacts it from its own logs and traces. The exception is a Bedrock-shaped upstream, where the AWS SDK rebuilds the header and the marking does not survive — avoid raising the gateway's log level there while this is enabled. Logging on the upstream itself is yours to check in every case.
Configure It
AISIX Cloud
In the Dashboard, open the resource's form and expand Advanced, then set Forward caller JWT in header. For provider keys, the field lives inside the Request overrides JSON block in the same section.
Through the Admin API, set it on the provider key's request block:
curl -X PATCH "$ADMIN_ENDPOINT/api/provider_keys/$PROVIDER_KEY_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"request": {
"forward_jwt_header": "authorization"
}
}'
The request block is replaced as a whole, not merged. Send the fields you already rely on — such as default_headers — alongside forward_jwt_header, or they are dropped. Read the current block first with GET /api/provider_keys/$PROVIDER_KEY_ID.
On a passthrough route or an MCP server the field is top-level:
curl -X PATCH "$ADMIN_ENDPOINT/api/environments/$ENV_ID/passthrough_routes/$ROUTE_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"forward_jwt_header": "authorization"}'
Open-Source Gateway
In resources.yaml:
provider_keys:
- display_name: internal-llm
provider: openai
api_base: https://llm.internal/v1
api_key: ${INTERNAL_LLM_KEY}
request:
forward_jwt_header: x-user-jwt
passthrough_routes:
- name: system-server
path_prefix: /passthrough/system
target_url: https://erp.internal
provider_key: internal-llm
forward_jwt_header: authorization
mcp_servers:
- name: system-server-tools
type: openapi
url: https://erp.internal/api/v1
forward_jwt_header: authorization
Verify It
Send a request with a JWT the gateway trusts, then check what the upstream received. Against an upstream you control, log the incoming headers for one request:
curl -X POST "$GATEWAY_ENDPOINT/v1/chat/completions" \
-H "Authorization: Bearer $AGENT_JWT" \
-H "Content-Type: application/json" \
-d '{"model": "internal-llm", "messages": [{"role": "user", "content": "hello"}]}'
The upstream should see the same token the client presented, in the header you configured. Repeat the request with a caller API key instead of the JWT: the header must be absent.
Related Reading
- JWT Authentication — verifying the inbound token in the first place.
- Claim Mappings — resolving a verified identity to a caller API key.
- MCP Upstream Authentication — the gateway's own credential for an MCP server, which this setting is independent of.