Documents the bearer-token dispatch chain implementation (plan §5 rules 0-5)
that was completed in commit 625e414. The implementation supports three
token types simultaneously: master_key, admin_key, and search UI JWTs.
Key features:
- Deterministic dispatch chain with 5 rules
- X-Admin-Key short-circuit for admin endpoints
- Constant-time comparison for all opaque tokens
- JWT validation with rotation support (primary + previous secrets)
- 62 unit tests covering all acceptance criteria
- Rate-limit hooks for Phase 6 multi-pod deployment
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5.2 KiB
5.2 KiB
P2.7 Auth: Bearer-Token Dispatch Implementation
Summary
Implemented the bearer-token dispatch chain per plan §5 rules 0-5 with full test coverage. The implementation supports three token types simultaneously: master_key, admin_key, and search UI JWTs.
Implementation Details
Rule 0 - Dispatch-Exempt Check (is_dispatch_exempt)
Endpoints that bypass all auth checks (handler decides auth):
GET /health- unauthenticated liveness probe (Meilisearch-compatible)GET /version- unauthenticated version endpoint (Meilisearch-compatible)GET /_miroir/ready- unauthenticated readiness probe (plan §10)GET /_miroir/ui/search/locale/*- unauthenticated public locale fetchPOST /_miroir/admin/login- credentials in bodyGET /_miroir/ui/search/{index}/session- auth persearch_ui.auth.modeGET /ui/search/{index}- public SPA entry point
Note: GET /_miroir/metrics requires admin_key (NOT exempt)
Rule 1 - JWT-Shape Probe
probe_jwt_shape(): Checks if token has JWT structure (three dot-separated base64url segments)validate_jwt(): Full JWT validation with:- HMAC-SHA256 signature verification
- Expiration checking with 30s leeway
- Support for dual secrets during rotation (primary + previous)
- Key ID (
kid) header to identify which secret signed the token
Rule 2 - Admin-Path Opaque-Token Match
is_admin_path(): Checks if path starts with/_miroir/- For admin paths: only
admin_keyis accepted via Bearer token - Master key is explicitly rejected on admin paths
Rule 3 - Master-Key Match
- For non-admin paths: only
master_keyis accepted via Bearer token - Admin key is explicitly rejected on non-admin paths
Rule 4 - Mismatch
- Missing Authorization header on auth-gated endpoints → 401
miroir_invalid_auth - Invalid token → 401
miroir_invalid_auth - JWT-shaped token that fails validation → 401
miroir_jwt_invalid
X-Admin-Key Short-Circuit
check_x_admin_key(): Independent header check for admin endpoints- Provides alternative auth mechanism for admin operations
- Uses constant-time comparison
Security Features
Constant-Time Comparison
- All opaque-token comparisons use
subtle::ConstantTimeEq - Prevents timing side-channel attacks on secret key values
- Test
constant_time_no_timing_leakverifies no measurable delta between "all bytes wrong" and "one byte wrong"
Rate-Limit Hooks
RateLimitBucketenum defines bucket key types:AdminLogin(String)-miroir:ratelimit:adminlogin:<ip>SearchUi(String)-miroir:ratelimit:searchui:<ip>
- Phase 2 uses in-memory stub (always allows)
- Phase 6 will back with task store (Redis/SQLite)
Test Coverage
All acceptance criteria verified with unit tests:
- ✅ Every row in plan §5 rule 5 exempt list has a unit test
- ✅ Opaque token on
/_miroir/*matches only admin_key; never master_key - ✅ Opaque token on other paths matches only master_key; never admin_key
- ✅ Missing Authorization on auth-gated endpoints → 401
miroir_invalid_auth - ✅
X-Admin-Keyalone gates admin endpoints equivalently to Bearer admin_key - ✅ Constant-time compare: timing-injection harness shows no measurable delta
Total tests: 62 auth tests, all passing
Configuration
AuthState Structure
pub struct AuthState {
pub master_key: String,
pub admin_key: String,
pub jwt_primary: Option<String>, // SEARCH_UI_JWT_SECRET
pub jwt_previous: Option<String>, // SEARCH_UI_JWT_SECRET_PREVIOUS
pub seal_key: SealKey, // For admin session cookies
pub revoked_sessions: Arc<DashMap<String, ()>>,
pub admin_session_revoked_total: Counter,
}
Environment Variables
MIROIR_MASTER_KEY- Client-facing API key (overrides config)MIROIR_ADMIN_API_KEY- Admin API key (overrides config)SEARCH_UI_JWT_SECRET- JWT signing secret (primary)SEARCH_UI_JWT_SECRET_PREVIOUS- JWT signing secret (rotation overlap)ADMIN_SESSION_SEAL_KEY- Admin session cookie sealing key
Files Modified
crates/miroir-proxy/src/auth.rs- Core bearer-token dispatch implementationcrates/miroir-proxy/src/main.rs- AuthState initialization and middleware wiring
JWT Rotation Support
The implementation supports seamless JWT secret rotation:
- Pre-rotation: Only primary secret active
- During rotation: New primary + old primary as previous
- Post-rotation: Only new primary (previous removed)
Tokens signed with either secret are accepted during the rotation window. Old tokens continue to work until they expire naturally.
Middleware Stack
The auth_middleware is correctly positioned in the middleware stack:
csrf_middleware- runs firstauth_middleware- bearer-token dispatch- Extension layers
request_id_middleware- sets X-Request-Id headertelemetry_middleware- reads X-Request-Id, creates tracing span
Integration with Admin Sessions
The dispatch chain integrates with admin session cookies:
- If a sealed admin session cookie is present, it is unsealed
- Session ID is checked against revocation cache (Pub/Sub sync)
- Valid sessions are authenticated without requiring Bearer token
- CSRF validation applies to session-based auth (not Bearer tokens)