# Accept JWTs and API keys on one surface

> Add an API key method beside JWT Bearer in a surface's Caller Context, so internal callers use tokens from your identity provider and partner agents use API keys, both on one surface.

This guide adds an API Key method beside the JWT Bearer method in a surface’s Caller Context element, so one surface accepts two kinds of caller. For how an Access Point authenticates its callers, see [Surface routing model](/products/affinidi-trust-fabric/agent-gateway/concepts/surface-routing-model.md#access-point).

Internal users and agents often hold a token from your corporate identity provider, while partner agents can only send an API key. Without a second method, each group needs its own surface in front of the same managed agent, with its own configuration to keep in step.

Use this guide when:

- Callers from your own organisation present a JWT that your identity provider issued.

- Partner agents, or other callers outside your identity provider, present an API key.

- Both groups call the same managed agent.

You need only one method if every caller presents the same kind of credential. In that case, follow [Validate bearer tokens on a surface](/products/affinidi-trust-fabric/agent-gateway/how-to-guides/access/validate-bearer-tokens-on-a-surface.md) or [Restrict surface access with API key authentication](/products/affinidi-trust-fabric/agent-gateway/how-to-guides/access/restrict-surface-access-with-api-key.md) instead.

## Prerequisites

- Power User or Administrator role on the gateway.

- A surface whose Access Point is closed, the default for surfaces created from version 1.0.4. See [Access Point readiness](/products/affinidi-trust-fabric/agent-gateway/reference/surfaces/access-point.md#access-point-readiness).

- A JWT verification strategy for your identity provider. Create one as in step 1 of [Validate bearer tokens on a surface](/products/affinidi-trust-fabric/agent-gateway/how-to-guides/access/validate-bearer-tokens-on-a-surface.md).

- Keys for your partners, in one of two forms:

- A secret: a gateway secret with Secret Type ApiKey that holds the valid key values, comma-separated. See [Secrets](/products/affinidi-trust-fabric/agent-gateway/reference/authentication/secrets.md).

- A key per partner: keys you create for this surface on the Secrets page’s API Keys tab, one Client ID each. See [API Keys](/products/affinidi-trust-fabric/agent-gateway/reference/authentication/api-keys.md).

- A JWT from your identity provider and a partner API key, to send in the Confirm tests.

## Steps

Open the Caller Context element

- Open the surface on the canvas.

- Select the Caller Context node to open its config panel. If the surface has none, drag Caller Context from the Security & Policy category of the palette onto the edge between the Access Point and the Managed Agent, then select it.

The Authentication methods list shows one row per method. A new Caller Context starts with one JWT row.

Configure the JWT Bearer method

- On the JWT row, select Configure ↗. The full configuration editor opens at Method 1 settings, with Authentication Method set to JWT Bearer.

- In JWT Verification Strategy, select your identity provider’s strategy.

- Optionally, add Accepted Audiences. Type a value and press Enter.

- Leave Token Header at Authorization and Scheme at Bearer, where internal callers send their token.

Add the API key method

- 
Below the method list, select Add method, then the API key method that matches your partners’ keys:

- API Key (secret store): valid keys come from a gateway secret.

- API Key (provider): valid keys come from this surface’s own list on the API Keys tab.

The editor shows Method 2 settings for the new method.

- 
Under API Key Location, keep HTTP Header. With more than one method, each method reads a header or the client certificate.

- 
Keep HTTP Header Name at X-API-Key, or enter the header your partners send. It must differ from the JWT method’s Authorization header, because each method reads its own credential location.

- 
For API Key (secret store), select your secret in Secret ID. API Key (provider) needs no further field: its keys are the ones issued for this surface.

Check the method rows

Look at the Authentication methods list, in the editor or in the sidebar panel.

- The JWT row carries the Primary badge and reads “Reads the ‘authorization’ header”.

- The API Key or API Key provider row reads “Reads the ‘x-api-key’ header”, or the header you entered.

- Both rows show the ready icon. A row with a problem shows a warning icon and the problem in place of the “Reads” line. See Troubleshooting for each message.

Keep JWT as the primary method. The primary method checks a request that carries no credential, so such a caller gets the bearer token challenge.

Close the editor.

Allow only authenticated callers with a policy

On a public or legacy Access Point, a failed credential is handed to policy rather than refused. Add a Policy element so the surface also denies such a caller if Public access is switched on.

- 
In the left palette, find the Policy element under the Security & Policy category.

- 
Drag Policy onto the canvas and drop it on the same edge as the Caller Context element.

- 
Select the Policy node to open its config panel.

- 
In Policy Definition, select or create a policy with the following rule.

```rego
package surface.policy

default allow := false

allow if {
  input.source_auth.method == "jwt_bearer"
}

allow if {
  input.source_auth.method == "api_key"
}
```

input.source_auth.method is the method that verified the caller. Both API key methods report api_key. A caller whose credential failed has the method failed and falls through to default allow := false. See the [OPA policies reference](/products/affinidi-trust-fabric/agent-gateway/reference/surfaces/opa-policies.md#inputsource_auth) for the full input.source_auth schema.

- 
Close the editor.

To give the two groups different rules, add conditions to each allow block, such as a group claim for JWT callers. On an MCP surface, an MCP Tool Gating gate whose Condition (OPA policy) checks input.source_auth.method == "api_key" can hide internal-only tools from partner callers. See [Control MCP tool access with per-tool policies](/products/affinidi-trust-fabric/agent-gateway/how-to-guides/policies/control-mcp-tool-access-with-per-tool-policies.md).

Save the surface

Select the save icon (tooltip: Save changes) on the canvas, or press Cmd+S (macOS) / Ctrl+S (Windows/Linux).

Identity element on a surface with both methods

The Identity element’s From JWT Claims type needs every Caller Context method to be JWT Bearer, and an inbound From API Key type needs every method to be an API key method. On this surface, use From Payload (identity metadata) or leave the Identity element out, and tell callers apart in policy by input.source_auth.method. See [Identity element](/products/affinidi-trust-fabric/agent-gateway/reference/surfaces/identity.md).

## Confirm

Replace <ENTRY_POINT_URL> with the Entry point URL shown at the top of the surface canvas, <YOUR_JWT> with a token from your identity provider, and <PARTNER_API_KEY> with a key value from your secret or the Secret shown when you created the partner’s key.

Set the request body for your surface’s protocol once. The tests below send it.

BODY='{"jsonrpc":"2.0","method":"message/send","id":1,"params":{"message":{"messageId":"test-1","role":"user","parts":[{"kind":"text","text":"Hello"}]}}}'

BODY='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

### Test 1: request with a JWT returns 200

```bash
curl -X POST "" \
  -H "Authorization: Bearer " \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d "$BODY"
```

Expected: 200 OK and your managed agent’s response. The JWT Bearer method verified the caller.

### Test 2: request with an API key returns 200

```bash
curl -X POST "" \
  -H "X-API-Key: 

" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d "$BODY"
```

Expected: 200 OK and your managed agent’s response. The API key method verified the caller.

### Test 3: request with both credentials returns 401

```bash
curl -X POST "" \
  -H "Authorization: Bearer " \
  -H "X-API-Key: 

" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d "$BODY"
```

Expected: 401 Unauthorized, although each credential is valid on its own. A request carrying credentials for more than one method fails caller authentication, so a caller can’t choose which identity the gateway asserts. The gateway records an auth_failed [audit event](/products/affinidi-trust-fabric/agent-gateway/concepts/audit-log.md) with the reason ambiguous_credentials.

### Test 4: request without a credential returns 401

```bash
curl -X POST "" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d "$BODY"
```

Expected: 401 Unauthorized. The primary method checked the request, so an A2A surface answers with a WWW-Authenticate: Bearer header and the detail “Caller credential missing or invalid”. An MCP surface answers with JSON-RPC error -32603 and the same message.

## Troubleshooting

| Symptom | Likely cause | Fix |
| A method row shows “Methods 1 and 2 both read the ‘authorization’ header; each method needs its own credential location”. | The API key method’s HTTP Header Name is the same header the JWT method reads. Header names match regardless of case. | Configure the API key method and set HTTP Header Name to another header, such as X-API-Key. |
| A method row shows “Method 2 reads MCP _meta; with more than one method, each must read a header or the client certificate”, or the same message for the A2A message extension. | The API key method’s API Key Location is Protocol-Specific. | Configure the API key method and select HTTP Header under API Key Location. |
| The JWT row shows “JWT strategy must be selected”. | The JWT Bearer method has no JWT Verification Strategy. | Configure the JWT method and select your strategy. |
| The API Key row shows “API key secret must be selected”. | The API Key (secret store) method has no Secret ID. | Configure the API key method and select your secret. |
| A later JWT row shows “Authorization server discovery allows JWT Bearer only as the first caller authentication method: the discovery audience and challenge belong to that method”. | Authorization server discovery is on, and a second JWT Bearer method was added. | Remove the second JWT Bearer method. With discovery on, the JWT Bearer method stays first and Make primary is disabled. |
| The Identity element reports “From JWT Claims requires a Caller Context whose methods are all JWT Bearer. Remove the other methods, or change this extraction type.” | The surface’s Identity element uses From JWT Claims. | Change the extraction type, as in the Identity note above the Confirm section. |
| The Identity element reports “Identity ‘From API Key’ needs every Caller Context method to be API Key or API Key Provider”. | The inbound Identity element uses From API Key. | Change the extraction type, as in the Identity note above the Confirm section. |
| A partner’s request returns 401 although its API key is valid. | The partner’s client also sends an Authorization: Bearer header, so the request carries credentials for both methods. | Have the partner’s client send only the API key header. |
| A JWT caller gets 401 with a valid token. | The token reached the gateway in a header other than the JWT method’s Token Header, or it fails the strategy’s checks. | Send the token as Authorization: Bearer <token>. For issuer, audience and signature checks, see the troubleshooting in [Validate bearer tokens on a surface](/products/affinidi-trust-fabric/agent-gateway/how-to-guides/access/validate-bearer-tokens-on-a-surface.md). |
| A request without a credential returns 200 instead of 401. | The Access Point is public (Public access is on) or legacy, so a failed credential reaches policy, and no policy denies it. | Switch off Public access, or select Close Access Point in the legacy notice, and save. Alternatively, add the Policy element from the policy step. |

Keep API keys out of code

Store each partner’s API key in a secrets manager rather than in source code or configuration files, and give it to the partner through a secure channel. Rotate or revoke the key from the gateway when a partner leaves or a key is exposed, and check that your clients and logs keep the key value out of their output.

## Next steps

- [Control MCP tool access with per-tool policies](/products/affinidi-trust-fabric/agent-gateway/how-to-guides/policies/control-mcp-tool-access-with-per-tool-policies.md): restrict which MCP tools each group of callers can use.

- [Use JWT claims to control access to a surface](/products/affinidi-trust-fabric/agent-gateway/how-to-guides/policies/use-jwt-claims-to-control-access-to-a-surface.md): gate internal callers on group or tenant claims.

- [Retire shared API keys](/products/affinidi-trust-fabric/agent-gateway/use-cases/retire-shared-api-keys.md): see how per-partner keys and your identity provider fit together.

## Related

- [Caller Context element](/products/affinidi-trust-fabric/agent-gateway/reference/surfaces/caller-context.md#authentication-methods): the method list, how the gateway selects a method, and the rules for saving several methods.

- [API Keys](/products/affinidi-trust-fabric/agent-gateway/reference/authentication/api-keys.md): issue, revoke, and rotate a key for each partner.

- [OPA policies](/products/affinidi-trust-fabric/agent-gateway/reference/surfaces/opa-policies.md): the input.source_auth fields a policy reads.

- [MCP Tool Gating element](/products/affinidi-trust-fabric/agent-gateway/reference/surfaces/mcp-tool-gating.md): allow and deny gates over MCP tool names.
