Bearer tokens

Static tokens and JWTs in one definition - outgoing OAuth2 client credentials with a client secret or a private key JWT, and inbound verification with JWKS.

Overview

A Bearer token security definition describes credentials sent in the HTTP Authorization header with the Bearer prefix. One definition serves both directions of traffic:

  • Outgoing - Zato obtains a token from an identity provider using the OAuth2 client credentials grant and attaches it to outgoing REST calls, caching and refreshing it automatically.
  • Inbound - Zato verifies tokens that callers present when they invoke REST channels or MCP gateways.

There are two kinds of tokens:

  • Static tokens - a constant string that you configure once and that callers must present exactly. Verification is a constant-time comparison, there is no identity provider involved at all.
  • JWTs - tokens issued by an identity provider such as Keycloak or Microsoft Entra ID. Zato validates them locally against the provider's published JWKS keys - the provider is never called during request processing, so verification adds no per-request network traffic.

Definitions are managed in the dashboard under Security > Bearer tokens. The create and edit forms have two tabs - Dynamic for definitions backed by an identity provider and Static for constant tokens. Changes take effect immediately, without server restarts.

Definition fields

Fields of a dynamic definition:

FieldNotes
NameA unique name for the definition
Client IDThe OAuth2 client ID, used when obtaining outgoing tokens
AuthenticationHow Zato proves the client's identity to the token endpoint - Client secret or Private key JWT. The rows below the select change with it.
Client secretThe OAuth2 client secret, with the client secret method
Private keyWith the private key JWT method, the PEM private key that signs each token request. Its public half is what you register with the identity provider, and the key is stored encrypted and never shown again after saving.
AlgorithmThe signing algorithm of the private key JWT method - RS384 by default, or RS256, PS256 and ES384. The RS and PS algorithms take an RSA key, ES384 takes an EC P-384 key.
Key IDOptional kid sent in the header of each signed request, for providers that pick the verification key by its ID.
Assertion audienceOptional override of the aud claim of the signed request, which is the auth endpoint's URL unless the provider says otherwise.
CertificateOptional PEM certificate of the private key. With it set, the x5t and x5t#S256 thumbprint headers are sent, which Microsoft Entra ID uses to find the key.
Auth endpointThe identity provider's token endpoint, e.g. https://idp.example.com/realms/prod/protocol/openid-connect/token
Data formatFormat of the token request, JSON or form data
Grant typeThe OAuth2 grant, client_credentials by default
ScopesOptional scopes requested with each outgoing token
Extra fieldsOptional additional name=value fields for the token request

The assertion audience and the certificate are collapsed behind a Toggle options link in the private key rows, since most providers need neither.

Fields used for inbound verification, in the same form under Channel verification, collapsed behind a Toggle options link:

FieldNotes
IssuerThe expected iss claim. Defaults to the scheme and host of the auth endpoint.
JWKS URLWhere the provider publishes its signing keys. Defaults to the issuer's /.well-known/openid-configuration discovery document, whose jwks_uri is followed automatically.
AudienceThe expected aud claim. Required for inbound JWT verification - a definition without an audience never matches inbound traffic.
ClaimsOptional name=value pairs, one per line, that the token must include in addition to the standard claims.
Identity claimOptional name of the claim that identifies the person or application behind a token, e.g. preferred_username. With it set, a caller's identity is the definition's name, a slash and the claim's value, orders.agents/maria.johnson@example.com, rather than the definition's name alone.

Fields of a static definition:

FieldNotes
HeaderThe HTTP header holding the token, Authorization by default
PrefixThe prefix before the token itself, Bearer by default
TokenThe exact token value that callers must present

The definition is exported and imported with enmasse under the security key, with type: bearer_token - the fields, both the outgoing and the inbound ones, are in the enmasse reference.

Inbound REST channels

Assign a definition to a REST channel by selecting Bearer token/<name> in the channel's security dropdown. From then on, every request to that channel must present a valid token:

$ curl -H "Authorization: Bearer $TOKEN" http://localhost:11223/api/orders

A request without a token, or with one that does not verify, receives a 401 response with the challenge header defined by RFC 6750:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer

Edits to the definition propagate to running channels immediately - for instance, changing the audience makes previously accepted tokens fail on the very next request, without a restart.

Security groups and MCP gateways

Bearer token definitions can be members of API client security groups, next to Basic Auth and API key members. A group assigned to a channel accepts a request when any of its members matches the presented credentials.

  • Groups are managed under Security > Groups in the dashboard.
  • Each MCP gateway automatically creates its own group named mcp.<gateway-name> - add Bearer token definitions to that group to control which tokens may call the gateway.
  • Group membership changes are live - adding or removing a member is reflected in enforcement immediately, without restarts.
  • Rejections at the group level return 403, unlike direct channel assignments which return 401 with the Bearer challenge.

When a JWT matches a claim-filtered definition in a group, the identity that gets resolved is that specific definition - two definitions with different claim filters on one gateway resolve to different identities. The resolved identity flows into MCP sessions, audit entries and rate limiting, so a session initialized with one token cannot be reused by a caller whose token resolves to a different definition.

A definition with an identity claim resolves each token to the definition's name and the claim's value together, e.g. orders.agents/maria.johnson@example.com, and the same three places then tell people apart within one definition - a session belongs to the person who opened it, each person has rate-limit counters of their own and the audit log names the person. A token without the claim resolves to the definition's name alone, as before. This is what OAuth for MCP gateways builds on, with people signing in through the identity provider from inside their AI clients.

How gateways authenticate agents end to end, including the other credential types and what rejections log, is described under MCP gateway security.

JWT verification reference

Inbound JWTs are verified in this order - the first failing step rejects the token:

  1. The token is extracted from the Authorization header. The Bearer prefix is case-insensitive, per RFC 6750.
  2. The token's header is read without verification to learn the algorithm and the key ID. The only accepted signature algorithm is RS256 - anything else, including none and HMAC algorithms, is rejected outright. A key ID (kid) must be present.
  3. The signing key matching the key ID is looked up in the provider's JWKS document.
  4. The signature is verified and the standard claims are validated - exp, iss and aud are all required to be present. The issuer must equal the configured issuer and the audience must contain the configured audience.
  5. The configured claims are matched last. A scalar claim matches by equality, a list-valued claim, such as groups, matches by membership - the configured value must be one of the list's elements.

JWKS documents are cached for one hour. When a token arrives with a key ID that is not in the cached document, the document is refetched once - this is how identity provider key rotation is handled without restarts and without refetching the document for every token. Failed fetches are never cached, so a temporary provider outage does not lock callers out for the duration of the TTL.

If the JWKS URL points to an OpenID Connect discovery document rather than the keys themselves, its jwks_uri field is followed automatically - configuring the issuer alone is therefore enough for providers that publish a standard discovery document.

Identity provider walkthroughs

Keycloak

  1. Create a realm, e.g. prod, or use an existing one.
  2. Create a confidential client - Clients > Create client, enable Client authentication and Service accounts roles. The client ID and the secret from the Credentials tab go into the definition's Client ID and Client secret fields.
  3. Keycloak does not add an audience by default - create an audience mapper. In the client's Client scopes, open the dedicated scope, add a mapper of type Audience and set Included Custom Audience to the value you configure as the definition's Audience.
  4. To filter by claims, add a User Attribute or Hardcoded claim mapper that puts e.g. department into the token, then set department=Accounting in the definition's Claims.
  5. The definition's fields:
  6. Auth endpoint - https://keycloak.example.com/realms/prod/protocol/openid-connect/token
  7. Issuer - https://keycloak.example.com/realms/prod
  8. JWKS URL - https://keycloak.example.com/realms/prod/protocol/openid-connect/certs, or leave it empty to use discovery.

A test call:

$ TOKEN=$(curl -s -d grant_type=client_credentials \
    -d client_id=my-client -d client_secret=my-secret \
    https://keycloak.example.com/realms/prod/protocol/openid-connect/token | jq -r .access_token)

$ curl -H "Authorization: Bearer $TOKEN" http://localhost:11223/api/orders

Keycloak with a signed JWT

For outgoing calls, the same client can log in with a private key instead of the secret, which Keycloak calls the Signed JWT client authenticator:

  1. Create the definition with Authentication set to Private key JWT, paste the private key, keep RS384 or pick another algorithm and set a Key ID, e.g. orders-key-1.
  2. Save the definition, open it again and follow the Public key link in the private key rows - it serves the public half of the key as PEM and as a JWK set.
  3. In Keycloak, open the client, set Client authenticator on the Credentials tab to Signed JWT, pick the same algorithm and import the public key on the Keys tab, either as PEM or as the JWK set.
  4. The Get token link in the definition's row confirms that Keycloak accepts the signed request.

Microsoft Entra ID

  1. Register an application - App registrations > New registration - and create a client secret under Certificates and secrets.
  2. The token endpoint is https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token.
  3. Set the definition's Issuer to https://login.microsoftonline.com/<tenant-id>/v2.0 and the Audience to the application ID URI or the client ID of the API being protected.
  4. Scopes for the client credentials grant use the .default suffix, e.g. api://my-api/.default - put that in the definition's Scopes field.
  5. To filter by claims, assign app roles to the calling application and match them with a list-valued claim, e.g. roles=Orders.Read.

Outgoing bearer tokens

The same definition drives outgoing calls. When an outgoing REST connection or an outgoing FHIR connection uses a Bearer token definition, Zato requests a token from the auth endpoint using the client credentials grant, attaches it as Authorization: Bearer ... and caches it, refreshing it before expiry. The grant type, scopes, data format and any extra fields of the token request all come from the definition.

The definition's Authentication field selects how Zato proves the client's identity in that token request:

  • Client secret - the client ID and the client secret go in the request as client_id and client_secret form fields, or under the names set in the Client ID field and Client secret field options.
  • Private key JWT - no secret is sent at all. Zato signs a short-lived JWT, a client assertion as defined in RFC 7523, with the definition's private key and sends it as client_assertion, together with client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer. The assertion names the client ID as its issuer and subject, the auth endpoint as its audience, expires after five minutes and has a unique jti, so a captured assertion cannot be replayed. The provider verifies the signature with the public key you registered with it.

The private key never leaves the server - it is stored encrypted, the dashboard and enmasse exports never return it and the only thing a provider ever receives is a signature. Providers describe the same method under their own names, which the next section lists.

Static definitions work for outgoing traffic too - the configured token is attached as-is, with the configured header and prefix, which covers APIs that use fixed API tokens with Bearer semantics.

In connection types you build yourself with the Connector SDK, expired tokens follow the same idea in your own code - an invocation raises CredentialsExpired, the platform calls the connector's refresh_credentials and the invocation is retried once.

Private key JWT by provider

Each provider has a term of its own for a client that logs in with a signed JWT instead of a secret, and a place of its own where the public key is registered. In every case, the definition's Client ID is the provider's client ID, the Auth endpoint is the provider's token endpoint and the Private key is the key whose public half the provider holds.

Okta calls it Public key / Private key client authentication. Create an API Services app, choose that authentication method and add the public key under Public keys - the JWK set the definition's Public key link serves can be pasted as is. Set the definition's Key ID to the kid Okta shows for the key and keep the audience as the token endpoint, as Okta requires. Leave Require DPoP switched off, since the token request is a plain client credentials one.

Auth0 calls it Private Key JWT and configures it under the application's Credentials tab. Auth0 verifies the assertion's audience against the tenant's base URL with a trailing slash, so set the definition's Assertion audience to https://<tenant>.auth0.com/ and keep the Auth endpoint as https://<tenant>.auth0.com/oauth/token.

Keycloak calls it the Signed JWT client authenticator, with the public key or certificate imported on the client's Keys tab, as the walkthrough above shows.

Microsoft Entra ID calls it certificate credentials - the app registration holds a certificate rather than a bare public key, under Certificates and secrets. Fill in the definition's Certificate field with that certificate, so the request includes the thumbprint headers Entra ID uses to find it, and pick PS256 or RS256 as the algorithm. The Auth endpoint is https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token.

Ping Identity and ForgeRock call it private_key_jwt, the token endpoint authentication method of a client on PingFederate, PingOne and PingAM. Register the public key or the JWKS on the client and set the client's signing algorithm to the one the definition uses.

SMART Backend Services is the term certified EHRs use for the same login - Epic, Oracle Health, eClinicalWorks and the other systems that expose FHIR APIs to backend applications. The token URL comes from the server's .well-known/smart-configuration document, the scopes are the system/ ones, e.g. system/Patient.read, and the public key is registered with the EHR as a JWK set. The FHIR security page describes the setup in detail.

The method is also what FAPI and Open Banking profiles call for when they rule out client secrets - the same definition works with any provider that follows them.

OpenAPI console sign-in

A Bearer token definition also signs in to the OpenAPI console - the client ID is the username and the client secret is the password. The signed-in caller receives an OpenAPI document containing only the channels the definition is assigned to, directly or through group membership, and the console's try-it client enforces the same access rules.

Troubleshooting

Every rejection is logged in the server log with the definition's name, the channel and the correlation ID (cid). The messages below map each log entry to its cause.

Log messageCause
Invalid static bearer tokenThe presented token is not equal to the definition's static token
Bearer token definition ... is not configured for inbound useThe definition has neither a static token nor an audience - set the audience to enable inbound JWT matching
Bearer token is not a valid JWT (...)The token is malformed - not three base64 segments
Bearer token uses unsupported algorithm ...The token's alg header is not RS256
Bearer token has no key IDThe token's header lacks a kid field
No JWKS key matches key ID ... from ...No published key matches the token's kid, even after a refetch - the token comes from a different provider or the JWKS URL is wrong
Bearer token has expiredThe exp claim is in the past
Bearer token has a wrong audience, expected ...The aud claim does not contain the configured audience
Bearer token has a wrong issuer, expected ...The iss claim does not equal the configured issuer
Bearer token has an invalid signatureThe signature does not verify against the JWKS key - the token was tampered with or signed by a different key
Bearer token is invalid (...)Another validation error, e.g. a required claim (exp, iss, aud) is missing from the token entirely
Bearer token is missing claim ...A configured claim is not present in the token
Bearer token claim ... is ... instead of ...A configured scalar claim has a different value
Bearer token claim ... does not contain ...A configured value is not among the elements of a list-valued claim
JWKS document could not be fetched from ...The JWKS URL did not respond with success - check connectivity and the URL
Document from ... is not a JWKS documentThe URL responded but the payload has no keys field - the URL points at something other than a JWKS or discovery document
Invalid bearer token (groups)A token was presented to a group-protected channel but no group member matched it - the caller receives 403
Received neither Basic Auth, bearer token nor API key (groups)An anonymous request reached a group-protected channel - the caller receives 403
401 Unauthorized path_info ... (Bearer token)A request to a channel with a directly assigned definition was rejected - the caller receives 401 with the WWW-Authenticate: Bearer challenge

Errors of the private key JWT method are reported when the definition is saved, in the dashboard or by enmasse, or when a token is requested:

MessageCause
Bearer token definition ... needs a private key to use private key JWTThe authentication method is private key JWT but no key was given and none is stored yet
Private key could not be parsed as PEM -> ...The pasted text is not a PEM private key - it must start with -----BEGIN PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY----- or -----BEGIN EC PRIVATE KEY----- and must not be password-protected
Algorithm ... needs an RSA private key, found ...RS256, RS384 or PS256 was picked for an EC key
Algorithm ... needs an EC private key, found ...ES384 was picked for an RSA key
Algorithm ES384 needs a P-384 key, found curve ...The EC key is on a different curve - generate a P-384 one
Certificate does not match the private keyThe certificate in the Certificate field was issued for a different key
Bearer token for ... could not be obtained from ... -> 400 -> {"error":"invalid_client" ...}The provider rejected the signed request - the public key registered with it does not match the private key, the Key ID does not match the registered kid, the algorithm is not the one the client is configured with or the assertion audience is not what the provider expects
Bearer token for ... could not be obtained from ... -> 400 -> {"error":"invalid_grant" ...}The provider accepted the client but not the request - with Auth0 and Okta, check that the assertion audience and the scopes are the ones the application allows
Kafka connection ... cannot use bearer token definition ... because it authenticates with a private key JWTKafka clients log in with a client secret only - use a client secret definition for Kafka connections

Learn more