TokenValidator runs in-process inside the API service for sub-10ms token validation. No network call to the auth service is required on the hot path.
Pipeline Steps
Step Details
Step 1: Prefix Detection
Token type is determined by a simple string prefix match — no JWT decoding required.TokenInvalidError is raised immediately.
Step 2: Customer ID Peek
The JWT payload is decoded without signature verification to extract thesub claim (customer_id). This is needed to look up the correct public key for Step 3.
This is safe because the unverified payload is only used as a cache key — the actual verification happens in Step 3.
Step 3: JWT Decode + ES256 Verify
The JWT (everything after the prefix) is decoded and verified using the customer’s cached ES256 public key. Public key caching: Keys are loaded at startup and refreshed every 5 minutes from the auth service’sGET /keys/public/{customer_id} endpoint. Stored in-memory on the TokenValidator instance.
Errors at this stage:
TokenExpiredError— JWT has expiredTokenInvalidError— signature mismatch, malformed JWT, missing claimsTokenInvalidError—subclaim doesn’t match peeked customer_idTokenInvalidError—typclaim doesn’t match detected prefix
Step 4: Bloom Filter Revocation Check
A Redis bitmap bloom filter provides O(1) revocation checking. If any of the 7 bit positions are unset, the token is definitely not revoked.auth.revocation_log via POST /bloom/rebuild.
Step 5: Chain Verification
Each token type has required claims that must be present for the derivation chain to be valid:| Type | Required | Validated |
|---|---|---|
| App | — | (root, no chain) |
| Bearer | parent_jti, env | Parent was an app token |
| Agent | parent_jti, agent_id, rbac | Parent was a bearer token |
| Subagent | parent_jti, agent_id, rbac, depth | Parent was an agent token |
| Session | parent_jti, session_id | Parent was agent or subagent |
| Override | event_id | Bound to specific event |
RBAC Enforcement (Post-Validation)
RBAC is enforced after validation, as a FastAPI dependency on protected routes. It is not part of theTokenValidator itself.
Integration: Auth Middleware
TheAuthMiddleware in src/api/middleware/auth.py orchestrates the dual-mode flow:
Error Handling
| Error | HTTP Status | When |
|---|---|---|
TokenExpiredError | 401 | JWT exp is in the past |
TokenRevokedError | 401 | JTI found in bloom filter |
TokenInvalidError | 401 | Bad signature, missing claims, wrong type |
RBACDeniedError | 403 | Action not permitted by RBAC policy |
SignatureInvalidError | 401 | Action signature verification failed |
SessionExhaustedError | 429 | Session event counter exceeded max_events |
Configuration
| Environment Variable | Default | Description |
|---|---|---|
REDIS_URL | redis://localhost:6379/0 | Redis for bloom filter + session counters |
AUTH_SERVICE_URL | http://localhost:8001 | Auth service for key refresh |
BLOOM_FILTER_SIZE | 1000000 | Bits in bloom filter bitmap |
BLOOM_FILTER_HASH_COUNT | 7 | Number of hash functions |
REDIS_PUBLIC_KEY_CACHE_TTL | 300 | Public key cache TTL (seconds) |