# JWT Authentication for Poimen Memory Service ## Overview Memory service validates incoming requests using JWT tokens issued by Authentik OIDC provider. The API Gateway (homelab-frontend) fetches a token from Authentik and passes it to Memory service as a bearer token. Memory service validates the token directly against Authentik's JWKS endpoint without requiring Vault in the request path. ## Architecture ``` Client/Gateway ↓ (Authorization: Bearer ) Memory Service (http_server) ↓ validate_jwt_token() Authentik JWKS Endpoint (cached) ↓ (signature + claims validation) JwtClaims (sub, iss, aud, permissions, groups) ↓ (check has_capability()) Route Handler (ingest, query, vault, etc.) ``` ## Configuration ### Environment Variables Required for JWT mode: - `MEM_AUTH_MODE=jwt` — Enable JWT validation (default: apikey) - `AUTHENTIK_ISSUER` — Authentik OIDC issuer, e.g. `https://authentik.riotpiao.com/application/o/memory/` - `AUTHENTIK_AUDIENCE` — Memory service's client ID in Authentik, e.g. `poimen-memory` Optional: - `JWT_CACHE_TTL_SECS` — JWKS cache TTL in seconds (default: 3600) ### K8s Deployment ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: poimen-memory namespace: poimen spec: template: spec: containers: - name: memory image: registry/poimen-memory:latest env: - name: MEM_AUTH_MODE value: "jwt" - name: AUTHENTIK_ISSUER valueFrom: configMapKeyRef: name: poimen-config key: authentik-issuer-url - name: AUTHENTIK_AUDIENCE valueFrom: configMapKeyRef: name: poimen-config key: memory-client-id - name: JWT_CACHE_TTL_SECS value: "3600" # ... other env vars ``` ## Capabilities JWT tokens must include a `permissions` claim with one of: - `memory:read` — Read-only: query, skills, projects, vault browsing - `memory:write` — Write: ingest, source sync, vault generation - `*` — Wildcard: all capabilities (typically for homelab-admins group) Example permissions claim in token: ```json { "permissions": ["memory:read", "memory:write"] } ``` ## API Request Format ```bash # Fetch JWT from Authentik (typically done by API Gateway) TOKEN=$(curl -s -X POST https://authentik.riotpiao.com/application/o/token/ \ -d "grant_type=client_credentials&client_id=...&client_secret=...") # Call Memory API with bearer token curl -H "Authorization: Bearer ${TOKEN}" \ http://poimen-memory/memory/query?project=myproject&query=topic ``` ## Security Considerations 1. **Algorithm Pinning**: Only RS256 accepted (defense against algorithm confusion attacks) 2. **Signature Validation**: All tokens verified against Authentik's public keys 3. **Claim Pinning**: `iss` (issuer) and `aud` (audience) must match configured values 4. **Expiry Check**: Expired tokens rejected (60s clock skew tolerance) 5. **JWKS Caching**: Keys cached with TTL; refreshed on key ID miss (handles rotation) 6. **Token TTL**: Memory service does not cache validation results; each request re-validates ## Backward Compatibility Default `MEM_AUTH_MODE=apikey` preserves old behavior: - Checks `apikey` header against `MEM_API_KEY` env var - Grants synthetic `*` permission - Useful for local dev/test Switch to JWT by setting `MEM_AUTH_MODE=jwt`. ## Capability Checking in Handlers Each handler checks for required capability: ```rust // In ingest_handler (write operation) if !has_capability(&claims, "memory:write") { return HttpResponse::Forbidden().json(...); } // In query_handler (read operation) if !has_capability(&claims, "memory:read") { return HttpResponse::Forbidden().json(...); } ``` Wildcard permission `*` grants all. ## Testing Unit tests in `tests/it_jwt_auth.rs`: - Bearer token extraction - JWT claims structures - Permission validation - Wildcard permission handling ```bash cargo test --test it_jwt_auth ``` Example test: ```rust #[test] fn test_jwt_permissions_claim() { let claims = JwtClaims { permissions: Some(vec!["memory:read".to_string()]), ... }; assert!(claims.permissions.unwrap().contains(&"memory:read".to_string())); } ``` ## Troubleshooting ### "Invalid Authorization header format" - Ensure request includes `Authorization: Bearer ` (capital B) - Token must not be empty ### "JWT validation failed: Token validation failed" - Check token signature: ensure Authentik JWKS endpoint is reachable - Verify issuer matches `AUTHENTIK_ISSUER` env var - Verify audience matches `AUTHENTIK_AUDIENCE` env var ### "Missing capability: memory:write" - Token's `permissions` claim must include `memory:write` or `*` - Check Authentik app scope configuration includes `permissions` claim ### JWKS fetch timeout - Ensure Authentik is reachable from Memory pod - Check network policies / firewall rules - Verify `AUTHENTIK_ISSUER` URL is correct ## Related Files - `crates/mem-cli/src/jwt_validator.rs` — Token validation logic - `crates/mem-cli/src/http_server.rs` — Handler integration - `tests/it_jwt_auth.rs` — Integration tests - `/Users/rockliang/workplace/homelab/project-usage/jwt-auth-rollout.md` — Cluster-wide OIDC setup