feat: cache the frontier authn token call in redis - #23
Open
rohilsurana wants to merge 6 commits into
Open
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
rohilsurana
force-pushed
the
feat/cache-authn-token
branch
from
September 8, 2026 18:38
1cf7c34 to
c037ab6
Compare
rohilsurana
marked this pull request as ready for review
September 8, 2026 19:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Every request through this plugin makes an HTTP call to Frontier to exchange the
cookie or bearer for a user token. Two hundred requests from one browser tab
means two hundred identical calls.
What this does
It caches that exchange in Redis. The lookup is Redis first, then Frontier on a
miss. Redis is shared by every pod, so a token is fetched once for the fleet
rather than once per pod.
The default TTL is 5 seconds, and it is deliberately short. A cached token means
a change to someone's access is not noticed until the entry expires.
Caching needs Redis. Set
redis_hostand it works. Leave it unset andcache_ttldoes nothing on its own, so every request goes to Frontier exactlyas it does today. There is no deployment step beyond pointing it at a Redis.
The cache is three files:
cache.luais the whole chain at 131 lines,redis.luais the driver and its breaker, andjwt_decoder.luagains a guardagainst a malformed token.
access.luachanges only in where it asks for atoken and how it ends a failed request.
Config
redis_hostcache_ttl50turns caching offcache_cookie_names["sid"]cache_exp_skew2redis_timeout100redis_key_prefixfrontier:authn:redis_breaker_seconds10Plus
redis_port,redis_username,redis_password,redis_database,redis_ssl,redis_ssl_verify,redis_server_name,redis_keepalive_msandredis_pool_size. Full table in the README.An entry costs about 1KB in Redis, so size it as users active within the TTL
window, times 1KB.
What is cached, and what is not
the config that decides what an entry means. Sessions are never stored as
plaintext keys, and two routes that would resolve a credential differently
cannot share an entry.
consent cookies that change constantly, so keying on the whole cookie header
would miss on nearly every request.
share an entry.
locked out for the length of the TTL.
exp, minuscache_exp_skew. Since that is the only thing that sets the expiry, a tokenstill in Redis always has at least the skew left on it, so the read side never
has to check again.
authz_urlpermission check still runson every request.
credential will each fetch a token. They all write an equivalent entry, and
everything after that is served from Redis.
expwhen it is stored, and for the claims thatbecome headers when it is used. Nothing else about it is assumed, so one that
is valid JSON but not shaped like a JWT is refused rather than half applied.
How Redis behaves
It never fails a request. Redis is a cache, not an authority. A connect
error, a timeout, a bad reply, even a raise, gets logged and the plugin carries
on to Frontier. When a command fails, that worker stops trying that instance for
redis_breaker_seconds, so an outage cannot make every request pay the timeout.The pause is per instance, so one bad Redis does not stop the worker using
another. A wrong password or database index is a config mistake rather than a
broken instance, so those are logged without starting the pause.
Guard write access to this Redis. The plugin does not check the token
signature, with or without Redis. It trusts what Frontier hands back. So
anything that can write these keys can put a token of its choosing in front of
your upstream. The entries also hold live user tokens, which is more sensitive
than something like rate limit counters. Turn on auth and SSL if the instance is
shared or reachable from outside the cluster.
One behaviour change
If Frontier answers 200 but the plugin cannot find a token in the response, it
now returns 401. It used to pass nil into
set_headerand fail with a 500:This had to change, because nil cannot pass through a cache cleanly. 401 is also
the right answer. Frontier returns a proper error status for every real auth
failure, so this state only means the plugin is pointed at the wrong endpoint,
or
token_response_fielddoes not match what the server returns. The 401 cannothide a successful auth.
Performance
Measured against a real Frontier, a real Redis and Kong in DB-less mode.
Absolute numbers come from Docker on macOS, so read the gaps rather than the
values.
One session making requests as fast as it can for 20 seconds,
cache_ttlat 5:Four calls in a 20 second window is what a 5 second TTL should give. The same
client also got through 1.7 times as many requests, because it was not waiting
on an auth call every time.
Kong's own CPU per request, from the container's cgroup accounting, and latency
with the plain and cached routes interleaved:
So the Redis hop adds about 0.6ms and saves about 32ms.
Testing
The unit tests cover the key builder, the TTL clamp, hostile tokens and the
cache behaviour, with Redis stood up as a table so both paths can be driven
directly. The end to end suite runs against a real Frontier, with postgres and
SpiceDB, using a real session cookie from the mailotp flow, and counts actual
AuthToken calls from Frontier's own metrics.
Two things only a real gateway shows:
of them logs a warning about a missing one.
directions, which matters if the fleet is ever mid-upgrade.
Works on Kong 3.4 and later, using only modules that ship with Kong and
OpenResty.
Why not a node cache as well
An earlier version of this kept a shared memory cache on each pod in front of
Redis, so the common path would pay no network at all. It is not here because
it did not earn its keep. It needed a
lua_shared_dictadded tokong.confonevery gateway, it brought
mlcacheand a single-flight lock, and it neededextra machinery to stop a token going stale twice over on its way through two
caches. Three adversarial reviews found sixteen defects on this branch, and by
the end every open one lived in that layer and none lived in Redis.
The measurements say it was not buying much. The Redis round trip it would have
saved costs 0.6ms of latency and no measurable CPU: 0.330ms per request against
0.34ms for the node memory hit it replaced.