Auth (JWKS-cached)
Authentication verifies Clerk-issued RS256 JWTs against Clerk’s JWKS endpoint. The JWKS is
cached with a 1-hour TTL, and an asyncio.Lock prevents a thundering herd from hammering
Clerk’s endpoint the instant the cache expires. The check is double-checked inside the lock.
# backend/app/core/auth.py (excerpt)_jwks_cache: dict | None = None_jwks_cache_time: float = 0_jwks_lock = asyncio.Lock()JWKS_CACHE_TTL = 3600 # 1 hour
async def _get_jwks() -> dict: """Fetch and cache JWKS from Clerk with TTL-based expiration.
Uses asyncio.Lock to prevent thundering herd on cache expiry. Uses a persistent httpx.AsyncClient to avoid per-request TLS. """ current_time = time.time() if _jwks_cache is not None and (current_time - _jwks_cache_time) < JWKS_CACHE_TTL: return _jwks_cache
async with _jwks_lock: # Double-check after acquiring the lock if _jwks_cache is not None and (time.time() - _jwks_cache_time) < JWKS_CACHE_TTL: return _jwks_cache response = await _http_client.get(settings.clerk_jwks_url) response.raise_for_status() _jwks_cache = response.json() _jwks_cache_time = time.time() return _jwks_cacheThis is an excerpt — the shipped file also handles key rotation (falling back to a refetch
when a token’s kid is not in the cached set) and the full RS256 verification. The behavior
is covered by backend/tests/test_auth_cache.py, which asserts the cache serves from memory
within the TTL and refetches after it expires.
The authenticated user is exposed to routes as a FastAPI dependency, so your endpoints just
declare user: Annotated[User, Depends(get_current_user)] and stay clean.