Zero-trust API gateway for the OmniBioAI platform.
Single enforced entry point for all service traffic. Every request is authenticated, authorized, quota-checked, and audited before reaching any backend service.
Internet / Client / Studio
↓
api-gateway :8080 ← single entry point
↓
TraceMiddleware ← generates X-Trace-Id UUID
↓
AuthMiddleware ← JWT validation via IAM client + Redis cache
↓
PolicyMiddleware ← RBAC/ABAC via policy-engine
↓
HPCMiddleware ← GPU/CPU quota via hpc-policy-engine (compute paths only)
↓
AuditMiddleware ← async audit log via security-audit
↓
target service ← workbench / tes / toolserver / model-registry / rag
Failure policy:
| Layer | On failure |
|---|---|
| Auth | FAIL CLOSED → HTTP 401 |
| Policy | FAIL CLOSED → HTTP 403 |
| HPC quota | FAIL CLOSED → HTTP 403 |
| Audit | FAIL OPEN → ignored |
Every request that isn't /health or / passes through this exact chain
(app/main.py; middleware is registered LIFO, so TraceMiddleware —
added last — runs first):
Incoming JWT
│ Authorization: Bearer <jwt>
▼
IAM validation AuthMiddleware → IAMClient.validate(token)
│ — Redis-cached (key "iam:{token}", TTL 300s) first,
│ else POST {IAM_URL}/auth/validate (1 retry on timeout)
│ — no token or a rejected token → 401, fail closed
▼
Policy PolicyMiddleware → PolicyClient.evaluate(...)
│ — POSTs user_id/email/roles/permissions/action/resource
│ to {POLICY_URL}/policy/evaluate
│ — deny → 403, fail closed
│ (HPCMiddleware runs next, compute paths only —
│ GPU/CPU quota via {HPC_URL}/jobs/evaluate, also
│ fail-closed)
▼
Forward Authorization header
│ gateway.py rebuilds the outgoing header set from
│ scratch (never re-forwards the raw incoming
│ "authorization" key) and sets
│ Authorization: Bearer <token> from
│ request.state.token — the exact string
│ AuthMiddleware already validated, not a re-parse
│ of the original header — so the backend service
│ can independently re-verify identity itself.
▼
Backend services proxy.forward() to the resolved SERVICE_MAP target
(workbench / tes / toolserver / model-registry / rag)
The gateway never decodes a JWT itself — no local HS256/RS256 verification,
no JWKS client. Every validation is a remote call to auth-service's
/auth/validate (Redis-cached), delegating entirely to the shared identity
layer described in omnibioai-auth's README rather
than duplicating its verification logic.
Alongside the re-forwarded Authorization header, the gateway also injects:
| Header | Value | Notes |
|---|---|---|
X-User-Id |
The authenticated user's ID | Unsigned — a convenience header for services that don't want to re-decode the JWT; the re-forwarded Authorization bearer token is the only header a backend should actually trust for authorization decisions |
X-Internal-Service |
gateway |
Marks the request as gateway-verified/internal |
org_id/org_role are resolved into request.state.user during IAM
validation but are not propagated as their own headers today — a
backend that needs them re-derives them from the forwarded JWT itself
(/auth/validate or a local jwt_verify.py, same as Control Center and
Security Audit do).
See "Forward Authorization header" in the pipeline above — this is not a service-to-service token, it is the caller's own validated bearer token, forwarded as-is so the backend service can verify it independently rather than trusting the gateway's decision blindly.
TraceMiddleware (outermost — runs first) generates or echoes an
X-Trace-Id UUID, attaches it to request.state.trace_id, sets it on the
response, and it's propagated on to the backend request. Every
middleware's own audit events (auth/policy/HPC denials, the final
upstream-forward event) carry this same trace ID, so a single request can
be followed end-to-end through the audit stream.
There is currently no separate service-to-service credential (no service
JWT, no mTLS, no API key) — the gateway calls backend services carrying
only the unsigned X-Internal-Service: gateway marker plus the forwarded
user bearer token. Backend-to-backend trust within the compose network is
not independently authenticated at the gateway layer; each backend service
is expected to validate the forwarded JWT itself if it needs a real
identity guarantee.
- JWT authentication via IAM client (Redis-cached, sub-ms validation)
- RBAC/ABAC policy enforcement on every request
- GPU/CPU quota governance for compute requests
- Async audit logging via Redis Streams (never blocks requests)
- Original bearer-token forwarding to backend services (see Authentication Pipeline)
- Distributed trace ID propagation (X-Trace-Id header)
- Redis pub/sub cache invalidation on logout
- Rate limiting on auth endpoints (via nginx — 10 req/min, burst 5)
Middleware is applied LIFO — last added runs first for requests:
| Order | Middleware | Responsibility |
|---|---|---|
| 1 | TraceMiddleware | Generate X-Trace-Id, attach to request state |
| 2 | AuthMiddleware | Validate JWT via IAM client |
| 3 | PolicyMiddleware | RBAC/ABAC authorization decision |
| 4 | HPCMiddleware | GPU/CPU quota check (compute paths only) |
| 5 | AuditMiddleware | Fire async audit event to Redis Streams |
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/health |
GET | — | Gateway health check |
/auth/verify |
GET | JWT | Verify token (used by nginx auth_request) |
/{service}/{path} |
GET/POST/PUT/DELETE | JWT | Proxy to target service — no /api prefix |
curl http://localhost:8080/health
# {"status": "ok"}curl -H "Authorization: Bearer <token>" \
http://localhost:8080/tes/jobs| Path | Target service |
|---|---|
/workbench/* |
workbench :8000 |
/tes/* |
tes :8081 |
/toolserver/* |
toolserver :9090 |
/model-registry/* |
model-registry :8095 |
/rag/* |
rag :8096 |
LIMS is not proxied through this gateway's SERVICE_MAP — it's reached
via a separate route.
| Header | Description |
|---|---|
Authorization |
The caller's original bearer token, re-forwarded verbatim (see Authentication Pipeline) |
X-Trace-Id |
UUID per request for distributed tracing |
X-User-Id |
Authenticated user ID (unsigned convenience header) |
X-Internal-Service |
Marks request as internal (gateway-verified) |
cd ~/Desktop/machine/omnibioai-studio
docker compose up -d api-gatewayAccess: http://localhost:8080
Set in omnibioai-studio/.env:
| Variable | Default | Description |
|---|---|---|
IAM_URL |
http://omnibioai-auth:8000 |
Auth service URL |
POLICY_URL |
http://omnibioai-policy-engine:8001 |
Policy engine URL |
HPC_URL |
http://omnibioai-hpc-policy-engine:8002 |
HPC policy URL |
REDIS_URL |
redis://redis:6379 |
Redis for token cache + pub/sub |
JWT_SECRET |
— | JWT signing secret (auto-generated) |
ROUTE_TIMEOUT |
15 |
Upstream request timeout (seconds) |
cd ~/Desktop/machine/omnibioai-api-gateway
pytest tests/ -v --cov=app
# 33 tests passing
# 74% coverage
# Covers: auth middleware, policy middleware, HPC middleware,
# trace middleware, config, gateway router| Layer | Technology |
|---|---|
| Framework | FastAPI + Uvicorn |
| Auth | IAM client (httpx async + Redis cache) |
| Cache invalidation | Redis pub/sub |
| Tracing | UUID trace IDs via middleware |
| Proxying | httpx reverse proxy |
| Service | Role |
|---|---|
omnibioai-auth |
JWT issuance and validation |
omnibioai-iam-client |
Async IAM client with Redis cache |
omnibioai-policy-engine |
RBAC/ABAC authorization decisions |
omnibioai-hpc-policy-engine |
GPU/CPU quota governance |
omnibioai-security-audit |
Async audit event consumer |
omnibioai-security-sdk |
SDK wrapping the full security stack |
omnibioai-studio |
Manages gateway container lifecycle |
Apache 2.0
Part of the OmniBioAI platform.