API security for the CTO: identity, gateway and tokens, without trusting blindly
By Dorian Chávez · founder of Hábil and integration architect ·
When to revalidate the token behind the gateway, how APIs call each other, introspection or local validation, logout and how long a token should last.
A frequent API security architecture today has three pieces: an identity provider that issues the tokens —Keycloak, Microsoft Entra ID, Okta or another—, a gateway that receives all the traffic —APISIX, Kong, Apigee, Azure API Management or another— and OAuth 2.0 as the language in which permissions are requested and presented. It is a good architecture. The problem shows up in the questions nobody wrote down when designing it: does the service behind the gateway check the token again? What happens when one API calls another? If the user logs out, does their token stop working? How long should a token last?
This article answers those questions for whoever has to sign off on the architecture: the CTO of a bank, a fintech, an insurer, a retail chain or any regulated company. Each answer comes with its condition, because in security there is almost never an "always."
Three types of route, three levels of trust
Before talking about tokens, it helps to classify each route of the API:
| Route type | What it is for | What it proves | What it doesn't prove |
|---|---|---|---|
| Public | Catalogs, general information, service health | Nothing | Who is calling |
| With API key | Identifying a consumer, measuring and limiting their usage | That the caller has the key | Who the person is, what they consented to, or whether the key was stolen |
| With OAuth token | Access with permissions, on behalf of a system or a person | With an OAuth access token configured for this API: lets you verify the attributes the profile and the provider issue —for example, issuer, audience, validity and permissions— through JWT validation or introspection | That the person is still authorized right now, if the token is long-lived |
An API key identifies, but doesn't authorize well: it carries no fine-grained permissions, no indication of who it is addressed to, no standard expiry, no delegation [1][2]. It works for simple consumption, metering and quotas. For an integration between companies (B2B) with sensitive data, OAuth 2.0 with client credentials is the better fit: the partner system obtains, via API, a short-lived token with narrow permissions, addressed to your API [3][4]. This route applies when the system acts on its own behalf or under a prior agreement; it does not replace a person's consent. If you keep API keys: one per consumer and per environment, stored as a secret, rotated and never in the URL, because URLs end up written in the logs of the gateway, the proxies and the monitoring tools, where anyone with access can read them [2].
Should the service behind the gateway check the token?
It is the question that generates the most arguments, and the short answer is: being behind the gateway does not grant trust by itself. The NIST zero trust framework says so: network location does not imply trust, and the implicit trust zone must be as small as possible [5].
The full answer separates two things that are often confused:
- Validating the token (is it authentic, did my provider issue it, is it meant for me, is it still valid?).
- Authorizing the operation (can this person or this system read this account, this policy, this order?).
The second, fine-grained authorization on the object, must be evaluated where the business rule and the relationship to the object are available: normally in the service or in a policy component integrated with it. The gateway can complement it with coarse-grained controls, but it doesn't replace that rule. A generic permission like "read payments" doesn't decide whether someone can read another customer's payment. That mistake has a name and is the first on OWASP's list of API risks [6][7].
The first can be delegated to the gateway, but only if all of these conditions are met [5][8][9]:
- The service cannot be reached by any path other than the gateway: not from another network, not from a neighboring service, not through an administrative route.
- The channel between the gateway and the service is authenticated, for example with mutual TLS.
- The gateway removes any identity header that arrives from outside and sets its own, signed (for example, an internal token) or over an authenticated channel, so that the service can verify it comes from the gateway.
- The route configuration is inventoried, tested and fails closed: a route without a rule is not left open.
If a condition is not met, document another policy enforcement point that verifiably validates the caller's identity and context; if none exists, the service must do it. And there are cases where it should validate it even if all four are met: when it handles money or personal data, when it decides based on data in the token, or when it serves several customers or domains.
A configuration detail worth its weight in gold: in some gateways, the plugins that validate API keys or JWTs forward the original credential to the service behind them unless configured otherwise, and the OpenID Connect one, with bearer_only set to false, does not strictly require a bearer token [10][11]. These are the values documented in APISIX 3.19 [vendor]; confirm the installed version and the effective configuration. Reviewing those default values is part of the architecture, not a detail.
When one API calls another
Inside the architecture, a service needs to call another. There are five common patterns, and each serves a different purpose [12][13][14]:
| Is there a user behind it? | Pattern | Use it when | Risk if misused |
|---|---|---|---|
| Yes | Forward the user's token | The target service is declared as a recipient of the token | The intermediary holds a token that works for other services |
| Yes | Exchange the token (token exchange, RFC 8693) | The target needs to know who the user is, but with its own token, reduced and addressed to it | The exchange allows requesting any permission for any target |
| No | The service's own credentials | Technical work, batch processes, events, reconciliation: there is no user behind it | Attributing to a user an action the system performed |
| Yes | Short-lived internal token | The gateway swaps the external token for an internal one, valid only inside | Unintentionally creating a second, ungoverned identity provider |
| No | Only mutual TLS between services | It is enough to know which service is calling | The certificate proves the service, not the user's permission |
A rule that reduces the risk of improper reuse: each service verifies that the token was addressed to it. The field that says so is called the audience (aud), and if the service is not in it, the token is rejected [15][16]; to restrict the destination when requesting it, there are resource indicators (RFC 8707) [30]. This avoids the mistake of "blind forwarding" (token passthrough) and its best-known consequence, the confused deputy: a service that, with a token that isn't its own, does something the user never authorized. For example, a reporting service that receives a token issued for the payments service and, with it, ends up able to execute payments.
For the chain "A calls B on behalf of the user," the recommended pattern is token exchange: A swaps the token it received for a new one, addressed to B and with fewer permissions [12]. In Keycloak, the standard V2 feature is enabled by default, but the requesting client must be confidential and have Standard token exchange enabled; standard exchange operates between clients of the same realm [17]. And NIST proposes something similar for microservices: swapping the external token at the entry point for a short-lived internal one, and verifying it at every hop [8].
Trust the token or ask the provider?
There are two ways to know whether a token is good:
| Method | Fits when | Advantage | Limit |
|---|---|---|---|
| Local validation of the JWT (signature with the provider's public keys, issuer, audience, expiry) | High volume, low latency | Doesn't depend on the provider being available on every request | Doesn't find out if the token was revoked after being issued |
| Introspection (RFC 7662): asking the provider | Opaque tokens, or actions where current state matters | Answers whether the token is still active now | Adds latency and dependence on the provider |
| Mixed | High-volume regulated APIs | Local validation for the common case, introspection for the critical | You have to decide and document what is "critical" |
Validating locally is not just "the signature passed": you have to fix the expected issuer, accept only the intended algorithms, check audience, expiry and permissions, and obtain the keys from a trusted place, because the provider rotates them: the service caches the public keys and, when a token arrives signed with a key it doesn't know, fetches them again instead of rejecting it or accepting it blindly [18][19]. And a cost the CTO signs off on: with introspection on every request, the identity provider sits on the critical path of every operation; if it goes down or slows down, everything goes down or slows down. And if the result of an introspection is cached, the cache time cannot be longer than the revocation window the business accepts: a five-minute cache contradicts a promise of "immediate" revocation.
Logging out doesn't turn off tokens already issued
An operationally relevant effect, one that often surprises a security committee: when a user logs out, the access token they already had remains valid until it expires, for any service that validates it locally. OpenID Connect's logout mechanisms end the session and notify the applications, but they don't invalidate the access tokens already in circulation [20][21]; Keycloak's documentation warns about it explicitly [17].
That is why token lifetime is designed in layers:
- Short access tokens, so that the exposure window is small.
- Revocable refresh tokens, bound to the client and, in public applications, with rotation [3].
- Introspection in the operations where current state matters: transfers, beneficiary changes, bulk downloads, administrative actions.
- Revocation (RFC 7009) when the provider supports it [22].
- Revocation events between systems (OpenID CAEP), which are already a final standard; provider support is still uneven [23].
How long should a token last?
There is no "standard" duration in any specification. OAuth leaves token lifetime to the provider [4]; the bearer token standard recommends one hour or less [24]; and the open banking security profile (FAPI 2.0) calls for short tokens without setting a figure [25]. What there are, are initial design ranges —an editorial criterion, not a standard—; adjust them to exposure, revocation window, re-authentication and provider availability:
| Token | Starting point | Comment |
|---|---|---|
| A person's access token | 5 minutes; 1 to 5 for payments and administration | Only if the refresh token is rotated and bound to the client; otherwise a somewhat longer lifetime is advisable, compensating with introspection on the critical |
| Access token between companies (client credentials) | 5 to 15 minutes | Renewed by authenticating the system again |
| Refresh token | As long as the session lasts: inactivity and maximum | Don't silently renew an inactive session |
| "Offline" (long-lived) token | Only as an approved exception | With an explicit maximum and revocation |
Keycloak's out-of-the-box values [vendor] are along those lines. We verified them directly in its source code (main branch consulted on 6 October 2026), because the documentation doesn't publish them in full [26]. They are vendor figures and may change between versions:
| Keycloak setting | Default value |
|---|---|
| Access token lifespan | 5 minutes (1 minute in the "master" administrative realm) |
| Session: idle | 30 minutes |
| Session: maximum | 10 hours |
| Offline session: idle | 30 days (its maximum is disabled by default) |
In Keycloak, the refresh token of a normal session lives as long as the session allows. Before relying on these numbers, check those of your installation: per-client settings and imports change them.
When do you need OpenID Connect?
- OAuth 2.0 is enough when what is being protected is the access of an application or a system to an API: integrations between companies, internal processes, permissions by scope [4].
- OpenID Connect fits when the application needs interoperable authenticated identity, authentication claims or single sign-on (also authentication level, logout and re-authentication for sensitive actions) [27]. OAuth alone does not standardize that identity: OpenID Connect is the identity layer on top of OAuth.
- A frequent mistake: using the OpenID Connect ID token to call an API. The ID token is for the application that started the login; the access token is what gets sent to the API [27].
In open banking, the bar goes up
If your API falls into the world of open banking or financial data, the FAPI 2.0 profile (final standard) raises the bar: tokens bound to whoever uses them —with mutual TLS or DPoP—, strong customer authentication and protected request parameters [25][28][29]. It is not mandatory for all APIs, nor is it a Mexican obligation by itself: it can be a useful technical reference for high-value APIs when the applicable framework, a contract or the risk policy adopts it, and it does not replace the assessment of applicable regulatory obligations.
Where to start
- Classify your routes as public, with API key and with token, and check that each one is where it should be.
- Decide, service by service, who validates the token, with the four conditions above in writing.
- Verify the audience in each service and eliminate blind token forwarding.
- Review the default values of your identity provider and your gateway.
- Define your revocation window by type of operation, and adjust lifetimes and introspection to it.
How many of your routes would withstand that review today? The first assessment conversation is at no cost: you leave with the map of your routes by trust level and the gaps to address first. Write to us on WhatsApp.
References
- OWASP, REST Security Cheat Sheet (API keys). https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html
- OWASP, OAuth2 Cheat Sheet. https://cheatsheetseries.owasp.org/cheatsheets/OAuth2_Cheat_Sheet.html
- IETF, RFC 9700, Best Current Practice for OAuth 2.0 Security (2025). https://www.rfc-editor.org/rfc/rfc9700.html
- IETF, RFC 6749, The OAuth 2.0 Authorization Framework, §4.4 client credentials. https://www.rfc-editor.org/rfc/rfc6749.html
- NIST, SP 800-207, Zero Trust Architecture. https://csrc.nist.gov/pubs/sp/800/207/final
- OWASP, API Security Top 10 2023 (API1: Broken Object Level Authorization). https://api-security.owasp.org/editions/2023/en/0xa1-broken-object-level-authorization/
- OWASP, Authorization Cheat Sheet. https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html
- NIST, SP 800-207A, A Zero Trust Architecture Model for Access Control in Cloud-Native Applications. https://csrc.nist.gov/pubs/sp/800/207/a/final
- NIST, SP 800-204, Security Strategies for Microservices-based Application Systems. https://csrc.nist.gov/pubs/sp/800/204/final
- Apache APISIX,
key-authandjwt-authplugins (hide_credentials). https://apisix.apache.org/docs/apisix/plugins/key-auth/ · https://apisix.apache.org/docs/apisix/plugins/jwt-auth/ - Apache APISIX,
openid-connectplugin (bearer_only, introspection, JWKS). https://apisix.apache.org/docs/apisix/plugins/openid-connect/ - IETF, RFC 8693, OAuth 2.0 Token Exchange. https://www.rfc-editor.org/rfc/rfc8693.html
- OWASP, Identity Propagation Patterns Cheat Sheet. https://cheatsheetseries.owasp.org/cheatsheets/Identity_Propagation_Patterns_Cheat_Sheet.html
- IETF, RFC 8705, OAuth 2.0 Mutual-TLS Client Authentication. https://www.rfc-editor.org/rfc/rfc8705.html
- IETF, RFC 7519, JSON Web Token, §4.1.3 (aud). https://www.rfc-editor.org/rfc/rfc7519.html
- IETF, RFC 9068, JWT Profile for OAuth 2.0 Access Tokens. https://www.rfc-editor.org/rfc/rfc9068.html
- Keycloak, Server Administration Guide and Securing Applications Guide (audience, token exchange, sessions). https://www.keycloak.org/securing-apps/token-exchange · https://www.keycloak.org/documentation
- OWASP, JSON Web Token Cheat Sheet. https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_Cheat_Sheet.html
- IETF, RFC 7662, OAuth 2.0 Token Introspection. https://www.rfc-editor.org/rfc/rfc7662.html
- OpenID, Back-Channel Logout 1.0. https://openid.net/specs/openid-connect-backchannel-1_0.html
- OpenID, RP-Initiated Logout 1.0. https://openid.net/specs/openid-connect-rpinitiated-1_0.html
- IETF, RFC 7009, OAuth 2.0 Token Revocation. https://www.rfc-editor.org/rfc/rfc7009.html
- OpenID, CAEP 1.0 (Continuous Access Evaluation Profile). https://openid.net/specs/openid-caep-1_0-final.html
- IETF, RFC 6750, Bearer Token Usage, §5.3. https://www.rfc-editor.org/rfc/rfc6750.html
- OpenID, FAPI 2.0 Security Profile (final). https://openid.net/specs/fapi-security-profile-2_0-final.html
- Keycloak [vendor], source code:
Constants.javaandApplianceBootstrap.java(default values), main branch consulted on 6 October 2026; links to the latest commit of each file as of that date. https://github.com/keycloak/keycloak/blob/55e0a796c7d397027c766efcad1ba47706ba8f72/server-spi-private/src/main/java/org/keycloak/models/Constants.java · https://github.com/keycloak/keycloak/blob/3575af2b5bc19fa16d3f6caf39ad1fc869c3fdda/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java - OpenID, OpenID Connect Core 1.0. https://openid.net/specs/openid-connect-core-1_0.html
- IETF, RFC 9449, OAuth 2.0 Demonstrating Proof of Possession (DPoP). https://www.rfc-editor.org/rfc/rfc9449.html
- OpenID, FAPI 2.0 Message Signing (final). https://openid.net/specs/fapi-message-signing-2_0-final.html
- IETF, RFC 8707, Resource Indicators for OAuth 2.0. https://www.rfc-editor.org/rfc/rfc8707.html
- Control who has access →
- APIs ready for AI agents →
- SSO with SAML 2.0 in Financial Services: Enterprise Implementation →
Does your operation face these challenges?
Prefer email? Write to us at hola@habil.mx