Skip to content

Identity service

The identity service owns every credential and membership primitive on the platform. We placed users, organizations, roles, JWT signing keys, API keys, and refresh tokens in a single service because they share one security domain: a compromise of any credential type invalidates the others.

Other services never store passwords or signing keys. They delegate authentication to identity via gRPC and receive an AuthContext carrying user_id, org_id, role, and env. This keeps the trust surface small and replaceable.

DomainWhat identity owns
UsersEmail + argon2id password hash, soft-delete, is_test flag
OrganizationsName, slug (citext UNIQUE), soft-delete
Memberships(org_id, user_id) PK; roles owner | member
JWTRS256 signing-key rotation (nextactiveretired)
Refresh tokensRFC 6749 §5.1 rotation; reuse-detection revokes the family
API keysPrefix + argon2id hash; live / test env segregation
IdempotencyPer-request Idempotency-Key deduplication (sdk/idempotency)
  • gateway (Phase 7): will verify every inbound JWT / API key via the gRPC AuthService before forwarding to downstream services.
  • agents (Phase 1.1 auth retrofit): calls VerifyJWT and VerifyAPIKey to authenticate sessions and tool requests.

Identity uses the identity schema on the shared Postgres cluster (ADR-0002). Cross-service foreign keys are forbidden (ADR-0003); other services reference identity entities by UUID only.

Tables: identity.users, identity.organizations, identity.org_members, identity.api_keys, identity.auth_keys, identity.refresh_tokens, identity.idempotency_keys.

Shipped in Phase 2. The security hardening sub-tasks closed in Phase 2 are:

  • C2 — NetworkPolicy: server egress scoped to PgBouncer only.
  • C4-A — Refresh-token rotation + reuse-detection.
  • C4-B — Idempotency-key middleware.
  • C5 — Bootstrap Helm Job (idempotent owner + org creation).
  • M6 — JWT signing-key auto-rotation at startup.
  • OCI — GHCR image push + Helm chart wired into release pipeline.
flowchart LR
gateway["gateway\n(Phase 7)"]
agents["agents\nHTTP/SSE"]
identityHTTP["identity\nHTTP :8081"]
identityGRPC["identity\ngRPC :50051"]
pg[("Postgres\nidentity schema")]
pgbouncer["PgBouncer\n:6432"]
direct["Postgres\n:5432 (migrator)"]
gateway -->|VerifyJWT / VerifyAPIKey| identityGRPC
agents -->|VerifyJWT / VerifyAPIKey| identityGRPC
identityHTTP --> pgbouncer --> pg
identityGRPC --> pgbouncer
identityGRPC -.->|JWT key lookup| pg
classDef controlPlane fill:#10b981,stroke:#047857,color:#fff
classDef dataPlane fill:#3b82f6,stroke:#1d4ed8,color:#fff
classDef sandbox fill:#f97316,stroke:#c2410c,color:#fff
classDef external fill:#ef4444,stroke:#b91c1c,color:#fff
classDef persistence fill:#6b7280,stroke:#374151,color:#fff
class identityGRPC controlPlane
class identityHTTP,gateway,agents dataPlane
class pg,pgbouncer,direct persistence