Key Verification & Caching
When receiving federated actions, the server must verify the JWT signature using the issuer’s public key. This involves a 3-tier caching strategy to balance security with performance.
3-Tier Caching Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Verification Request │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Tier 1: In-Memory Failure Cache │
│ ├─ Purpose: Prevent repeated requests to unreachable instances │
│ ├─ TTL (network errors): 5 minutes │
│ ├─ TTL (persistent errors): 1 hour │
│ └─ LRU eviction when capacity exceeded │
└───────────────────────────────┬─────────────────────────────────┘
│ (cache miss or expired)
▼
┌─────────────────────────────────────────────────────────────────┐
│ Tier 2: SQLite Key Cache │
│ ├─ Purpose: Persistent cache of successful key fetches │
│ ├─ Key: (issuer, key_id) │
│ ├─ Stores: public key + expiration timestamp │
│ └─ If valid & not expired: verify signature immediately │
└───────────────────────────────┬─────────────────────────────────┘
│ (cache miss or expired)
▼
┌─────────────────────────────────────────────────────────────────┐
│ Tier 3: HTTP Fetch from Remote │
│ ├─ Endpoint: GET https://cl-o.{issuer}/api/me │
│ ├─ Find matching key by key_id │
│ ├─ On success: cache in SQLite, clear failure cache │
│ └─ On failure: record in failure cache │
└─────────────────────────────────────────────────────────────────┘Failure Types and TTLs
| Error Type | TTL | Reason |
|---|---|---|
| Network timeout | 5 minutes | May recover quickly |
| Connection refused | 5 minutes | Server may restart |
| 404 Not Found | 1 hour | Key doesn’t exist |
| 403 Forbidden | 1 hour | Permission denied |
| Parse error | 1 hour | Invalid response format |
Verification Flow
verify_action_token(token):
1. Decode JWT without verifying (extract issuer, key_id)
2. Check failure cache:
if failure_cache.has(issuer, key_id) and not expired:
return Error(CachedFailure)
3. Check SQLite cache:
if key_cache.has(issuer, key_id):
key = key_cache.get(issuer, key_id)
if key.expires_at > now:
return verify_signature(token, key.public_key)
4. Fetch from remote:
response = HTTP GET https://cl-o.{issuer}/api/me
if error:
failure_cache.record(issuer, key_id, error_type)
return Error(KeyFetchFailed)
public_key = find_key_by_id(response.keys, key_id)
if not found:
failure_cache.record(issuer, key_id, NotFound)
return Error(KeyNotFound)
key_cache.store(issuer, key_id, public_key)
failure_cache.clear(issuer, key_id)
return verify_signature(token, public_key)See Also
- Action Delivery - How actions are received
- Security - Trust model and spam prevention
- Identity System - Public key management