Rukon
Full-Stack Development3 min read

Logout Still Worked for 30 Seconds: Token Caches on Serverless

A token verification cache meant a signed-out session kept working for up to 30 seconds. Why evicting on sign-out does not fix it on serverless, and why the answer was a small measured bound.

By

AuthenticationCachingServerlessSupabase AuthFastAPISecurity

The problem: a request after logout still succeeded

If your API caches the result of verifying an access token, sign-out does not take effect until that cache entry expires. This is a short note on finding that in ResearchForge, a university project whose FastAPI backend verifies Supabase access tokens, and on why the fix was a number rather than a mechanism.

Each private request verifies the caller's token with Supabase Auth. One page load fires several API calls within about a second, so the backend kept a small in-memory cache of verified tokens to collapse that burst into one check. The window was 30 seconds. A live logout test against production showed what that meant: sign out, make a request with the old token, and the request succeeded. On a shared computer, "log out" has to mean it.

The comment above the constant had claimed revocation was effectively immediate. It was not, and a comment the value contradicts is worse than no comment.

Why evicting on sign-out does not fix it

The tempting fix is to delete the token from the cache when the user signs out. On a single long-running server that would work. The API runs as serverless functions, so there may be several warm instances at once, each with its own in-memory cache. Evicting on the instance that handled the sign-out leaves the others still holding the token. That fix looks complete in a code review and is racy in production.

Dropping the cache entirely would make revocation immediate, at the cost of a round trip to the auth service for every call in every page load. A shared cache would need a new piece of infrastructure for a university project's login. Neither was worth it for this.

The fix: a small, measured, documented bound

The time-to-live went from 30 seconds to 5. That still covers a page load's burst of calls, and bounds post-logout validity to roughly the time it takes to close the tab. The cache is also capped at 512 entries and cleared when full, because it is a latency optimisation and never a source of truth.

# It cannot be driven to zero without giving up the cache entirely, and it
# cannot be fixed by evicting on sign-out either: the API runs as several
# serverless instances ...
_CACHE_TTL_SECONDS = 5.0
_CACHE_MAX_ENTRIES = 512
pythonFrom src/api/auth.py.

The bound is asserted in a test so it cannot quietly grow again, and it is listed as a known limitation on the project page: a revoked session stays valid for up to about five seconds.

Measuring it on the deployed system

A live script signed in, confirmed a library request returned 200, signed out, and kept sampling with the old token:

Time after sign-outResponse
1.6 s200, still accepted
3.9 s200, still accepted
6.1 s401, refused
8.0 s to 20.7 s401 on every sample
From the ResearchForge evaluation evidence.

One detail from the evaluation is worth repeating: an earlier version of this live test asserted that the token was refused immediately after sign-out, and it failed. The assertion was wrong, not the system, because the system was documented to allow up to five seconds. A test should check the guarantee you actually made. The rest of the testing approach is in Testing an LLM App Without Calling a Paid API, and the account flows are in the ResearchForge case study.