Bearer tokens

Static tokens and JWTs in one definition - outgoing OAuth2 client credentials 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
Client secretThe OAuth2 client secret
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

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.

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

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:17010/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.

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 a flood of refetches. 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:17010/api/orders

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 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.

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.

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.

Enmasse

Bearer token definitions are exported and imported with enmasse under the security key, with type: bearer_token. All the inbound fields round-trip.

security:

  # A static definition
  - name: partner.static
    type: bearer_token
    static_token: Zato_Enmasse_Env.Partner_Static_Token

  # A JWT definition with inbound verification
  - name: keycloak.accounting
    type: bearer_token
    username: my-client-id
    password: Zato_Enmasse_Env.Keycloak_Client_Secret
    auth_endpoint: https://keycloak.example.com/realms/prod/protocol/openid-connect/token
    issuer: https://keycloak.example.com/realms/prod
    jwks_url: https://keycloak.example.com/realms/prod/protocol/openid-connect/certs
    audience: orders-api
    claims:
      - department=Accounting

groups:
  - name: api.orders.clients
    members:
      - partner.static
      - keycloak.accounting

mcp_gateway:
  - name: orders.mcp
    is_active: true
    url_path: /mcp/orders
    security_groups:
      - api.orders.clients

channel_rest:
  - name: api.orders
    service: orders.get-list
    url_path: /api/orders
    security: keycloak.accounting
FieldNotes
nameThe definition's name
typeAlways bearer_token
usernameThe OAuth2 client ID
passwordThe OAuth2 client secret
auth_endpointThe token endpoint
static_tokenThe exact token of a static definition - a definition is static when this field is set
static_headerHeader holding the static token, Authorization by default
static_prefixPrefix before the static token, Bearer by default
issuerExpected iss claim, defaults to the scheme and host of auth_endpoint
jwks_urlWhere the signing keys live, defaults to the issuer's discovery document
audienceExpected aud claim, required for inbound JWT matching
claimsA list of name=value entries the token must include
data_formatFormat of the outgoing token request
grant_typeThe OAuth2 grant for outgoing tokens
scopesScopes requested with outgoing tokens
extra_fieldsAdditional name=value fields for the token request

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

Learn more