A REST API in Amazon API Gateway enables authorizer result caching (`authorizerResultTtlInSeconds`) on its Lambda authorizer. What is cached and what is the cache key, and what goes wrong in production because of it?
answer
- the whole response is cached, not a boolean
- keyed on the token, not the method
- TTL equals your revocation window
- narrow methodArn replayed elsewhere
- never cache per-request context values
basics
~20 sAPI Gateway caches the authorizer's whole response — policy and context — keyed by the token for a TOKEN authorizer or the combined identity sources for a REQUEST authorizer. That creates a revocation lag equal to the TTL, and reuses a narrow policy on other methods.
solid answer
~50 sWith caching enabled, API Gateway stores the authorizer's entire returned response — the policy document and the `context` map — for the configured TTL, keyed by the identity source: the token value for a TOKEN authorizer, or the concatenated identity-source values for a REQUEST authorizer. Two failures follow. First, **stale authorization**: revoking a user or changing their permissions has no effect until the entry expires, so the TTL is your revocation window. Second, the **narrow-policy trap**: if the authorizer returns `Resource: event.methodArn`, that exact-method policy is replayed for the same caller's requests to *other* methods, which then fail with `403` until the TTL lapses. The usual fix is to return a wildcard covering the API and stage, accepting coarser granularity — or to drop the TTL to zero and pay for an authorizer invocation on every request. Remember the cached `context` is stale too, so never put per-request values in it.
code
javascript · 13 lines// Return a stage-wide resource so a cached policy stays valid on other methods.
exports.handler = async (event) => {
const parts = event.methodArn.split(':'); // arn:aws:execute-api:region:acct:rest
const [apiId, stage] = parts[5].split('/');
const resource = `arn:${parts[1]}:execute-api:${parts[3]}:${parts[4]}:${apiId}/${stage}/*/*`;
return {
principalId: 'user-42',
policyDocument: {
Version: '2012-10-17',
Statement: [{ Action: 'execute-api:Invoke', Effect: 'Allow', Resource: resource }],
},
};
};go deeper
Know that authorizer results are cached for a TTL to avoid invoking the function on every request, and that a change in permissions is not instant.
Explain that the whole response including context is cached, that the key is the token or the combined identity sources, and what the default and maximum TTL are.
Diagnose the two production symptoms on sight — sporadic 403s from a narrow resource ARN, and a revoked user who keeps working — and state the tradeoff you chose between cost, freshness and granularity.
Own the policy: what revocation latency the product will guarantee, whether authorizers may return per-method policies at all, and what the platform standard is for authorizer context so backends can rely on it.
## What caching is for A Lambda authorizer sits on the request path. Every call to the API costs an extra function invocation, its latency, and occasionally a cold start. Result caching exists so that a client making a hundred calls with the same token pays for one authorization decision rather than a hundred. On a REST API the TTL is set per authorizer via `authorizerResultTtlInSeconds`; the default is 300 seconds, the maximum is one hour, and setting it to `0` disables caching entirely. ## What is cached, and under what key API Gateway caches the **entire response object** your function returned — the `policyDocument` *and* the `context` map — not merely a yes/no. The key depends on the authorizer type: - **TOKEN authorizer**: the value of the single identity-source header, i.e. the raw token string. - **REQUEST authorizer**: the concatenation of all the identity sources you declared (headers, query-string parameters, stage variables). If you declared none, caching cannot be enabled — identity sources are what make a cache key possible. A useful side effect of declaring identity sources on a REQUEST authorizer with caching on: a request that omits one of them is rejected with `401` without invoking your function at all. The cache is per stage. Two stages of the same API do not share entries. ## Failure one — the revocation window This is the one candidates usually get to. You disable a user, revoke a session, downgrade a role. The next request from that caller carries the same token, hits the same cache key, and is served the previously cached Allow. Nothing you do to your identity store is consulted, because your function is not invoked. So the TTL is not a performance knob alone: **it is the maximum time a revoked caller keeps access.** Five minutes of lag is fine for most products and unacceptable for some. If you need immediate revocation, either set the TTL to `0` and pay per request, or keep a short TTL and accept a bounded window, and be honest about which you chose. Shortening tokens' lifetimes does not help by itself — a cached Allow keyed on a token is replayed regardless of what the token says, until the entry expires. ## Failure two — the narrow-policy trap This is the one that separates people who have run this in production from people who have read the docs. The obvious authorizer implementation returns: ```javascript Resource: event.methodArn // arn:...:api-id/prod/GET/orders ``` That is correct for the request in hand. But the cached entry is keyed on the **token**, not on the method. When the same client calls `POST /orders` a moment later, API Gateway finds the cached entry and evaluates the *stored* policy — which allows only `GET /orders` — against the new method ARN. Result: a `403` for a caller who is perfectly entitled, lasting until the TTL expires. The symptom is maddening: the first endpoint a client touches after a gap works, and the others fail, with the pattern shifting depending on call order. The standard fix is to return a policy whose resource covers the whole stage: ```javascript Resource: `arn:aws:execute-api:${region}:${accountId}:${apiId}/${stage}/*/*` ``` That trades granularity for correctness: the authorizer now says "this caller may use this API", and per-endpoint entitlement moves into the backend or into a scope check. If you genuinely need per-method policies from the authorizer, either disable caching or, on a REQUEST authorizer, include a path-derived identity source in the cache key so different paths get different entries — at the price of far more authorizer invocations. ## Failure three — stale context Because the `context` map is cached along with the policy, anything time-varying you put in it is wrong for every subsequent cached request: a request id, a timestamp, a resolved source IP, a freshly computed rate-limit bucket. Backends that log or branch on those values quietly attribute one request's data to many. Keep `context` to slow-moving identity facts — subject, tenant, entitlement tier — and derive per-request values in the integration. ## How to talk about the tradeoff Good answers frame it as a three-way tension: cost and latency (favouring a long TTL), revocation freshness (favouring a short one), and policy granularity (favouring either a wildcard resource or no caching). Say what you would measure — authorizer invocation count versus request count tells you the cache hit rate — and say what the business tolerance for a stale Allow actually is. And note that HTTP API Lambda authorizers have their own caching behaviour that you should verify rather than assume matches REST.
- A client reports that the first endpoint it calls works and every other endpoint returns 403 for a few minutes. What do you check first?The `Resource` the authorizer returns. If it is `event.methodArn`, the cached policy allows only the first method the client happened to call and is replayed for the rest until the TTL expires. Confirm by setting the TTL to zero temporarily — if the 403s vanish, the cache and the narrow resource are the cause.
- Your product requires that disabling a user cuts access immediately. How do you reconcile that with authorizer caching?Either set `authorizerResultTtlInSeconds` to `0` and accept an authorizer invocation per request, or keep a short TTL and enforce the revocation check downstream — the backend consults the session state for sensitive operations. Pick deliberately: with caching on, the TTL is the guaranteed upper bound on how long a revoked caller keeps working.
- Why can't a REQUEST authorizer use caching without declared identity sources?The identity sources are what the cache key is built from — their values are concatenated to form it. With none declared there is nothing to key on, so API Gateway will not enable caching. A useful side effect of declaring them is that a request missing one is rejected with `401` before your function is invoked.
saying these in an interview costs you the question
- Thinks only an allow/deny boolean is cached
- Assumes the cache is keyed by method or path
- Believes a short token lifetime bounds the revocation lag
- Returns event.methodArn and enables caching without noticing the conflict
- Puts request ids or timestamps into the authorizer context