Identity Service — Service Overview
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 03 platform-services · 02 DDD · 13 Security
1. Purpose
The identity-service is the authoritative store of who a principal is across Ghasi-eHealth. It owns authentication (credentials, MFA, sessions, refresh), device binding, federated identity (OIDC/SAML, Keycloak broker), service accounts, JWKS publication, and module licensing enforcement (what capabilities are available at a hierarchy node). It does not own authorization policy (see tenant-service).
2. Bounded context
Identity, AuthN & Licensing — classified as Generic / Supporting. Runs in-house because:
- Tenant isolation (RLS-backed credential store, no third-party user store by default).
- Offline device binding (device public keys feed encryption key derivation in content-service and patient-chart-service).
- Licensing ties to hierarchy nodes, which are tenant-sensitive and must be evaluated in-cluster.
- Per-MAU SaaS identity vendor costs grow poorly at national eHealth scale.
3. Responsibilities
| Area | What identity-service owns |
|---|---|
| Credentials | argon2id password hashing, WebAuthn public keys, magic links, breach-list checks |
| Sessions | Short-lived platform JWT (15 min), rotating refresh tokens (8h), revocation, concurrent session caps |
| MFA | TOTP enrollment, WebAuthn second factor, recovery codes, adaptive challenge (slice S4) |
| Devices | Registration, fingerprinting, public-key storage, offline-binding certificates (short-lived, revocable) |
| API keys | Tenant-scoped API key issuance, rotation, revocation, scoped permissions for M2M |
| Service accounts | Keycloak confidential clients, client-credentials flow, rotation, revocation |
| Federated identity | OIDC (generic, Keycloak broker, Firebase, Okta, Cognito), SAML 2.0 SP flows, JIT user provisioning, SCIM participation |
| JWKS | /.well-known/jwks.json RS256 rotation every 90 days, consumed by every other service |
| Account lifecycle | Registration, email verification, lock/unlock, suspend/reactivate, soft-delete, GDPR erasure participation |
| Access context aggregation | GET /identity/me/access-context composes roles (tenant-service) + memberships (tenant-service hierarchy) + effective licenses (internal licensing module) for UI gating |
| Module catalogue | Stable code registry (ehr.core, diag.laboratory, clinical.immunizations, ...), dependency graph, is_always_on flags |
| License assignments | Node-scoped exact / inherit-down licenses, status transitions (trial / active / suspended / expired / terminated), constraints (seats, expiry, usage caps), append-only history |
| Effective license resolution | Walk hierarchy ancestor chain; overlay with deeper-wins rule; cache effective set per (tenantId, providerId, nodeId) with 5 min TTL |
4. Non-responsibilities
| Area | Owner | Why not identity-service |
|---|---|---|
| Authorization policy (RBAC/ABAC) | tenant-service | AuthN vs AuthZ separation; prevents circular dependencies. |
| User profile & membership | tenant-service | A user may belong to many tenants; membership is a tenant aggregate. |
| Organizational hierarchy | tenant-service | Hierarchy nodes (facility / department / ward) live with tenant governance. |
| Tenant lifecycle | tenant-service | Identity references tenantId but does not create tenants. |
| Notification delivery | communication-service | Identity emits events; communication-service sends email/SMS/push. |
| Audit storage | audit-service | Identity produces audit events; audit-service centralizes retention. |
| Cross-tenant admin console UI backend | platform-admin-service | Identity exposes primitives; platform-admin-service consumes them. |
5. Upstream & downstream dependencies
5.1 Upstream
| Dependency | Pattern | Purpose |
|---|---|---|
| Keycloak 24 (realm-per-tenant or shared) | Broker | OIDC/SAML authentication front-end, user credential store option |
| External OIDC IdPs (Google, Firebase, Okta, Cognito, customer Azure AD) | ACL | Federated login, JIT provisioning |
| External SAML 2.0 IdPs | ACL | Enterprise SSO with encrypted assertions |
| KMS (AWS KMS / HashiCorp Vault) | Infrastructure | JWT signing keys, device certificate signing, API key HMAC secrets |
| tenant-service | Event consumer (tenant.activated, tenant.suspended, tenant.terminated, tenant.user.invited) | Provision shadow users, invalidate sessions on suspension, deactivate on termination |
| config-service | Pull | Environment / per-tenant runtime config (session timeouts, MFA policy defaults) |
5.2 Downstream consumers
| Consumer | Pattern | What they consume |
|---|---|---|
| All 26 other services | Conformist for JWT claims | Validate JWTs via JWKS; read sub, tid, tids, amr, context_node_id |
| tenant-service | Event consumer | identity.user.registered.v1, identity.user.suspended.v1 for membership bookkeeping |
| communication-service | Event consumer | Password reset, email verification, MFA enrollment completed |
| patient-portal-service, virtual-care-service | JWT consumer | Patient identity assertions, device trust |
| sync/offline path in patient-chart-service, document-service | Event consumer | identity.device.bound_for_offline.v1 for encryption key derivation |
| access policy engine (tenant-service) | Pull (GET /identity/internal/users/:id) | Provider identity package for context evaluation |
| kong edge | Pull (JWKS) | Validate JWTs at the gateway |
6. Slice involvement
| Slice | Scope | Milestone |
|---|---|---|
| S0 — Foundation | Email+password, session JWT + refresh, device registration, JWKS, service accounts, module catalogue seed, always-on licenses | M0 |
| S1 — Offline & licensing | Device-binding certificates for offline, API key management, effective-license resolver with inheritance, license assignment lifecycle | M1 |
| S2 — Federated identity | OIDC generic + Keycloak broker, JIT provisioning | M2 |
| S4 — Enterprise & security | SAML 2.0, adaptive MFA (risk-based), SCIM participation, session freeze, break-glass enablement | M3 |
7. Architectural freeze points
| Freeze | What is frozen | Frozen by |
|---|---|---|
| F03 | Branded value objects: UserId, TenantId, DeviceId, SessionId, APIKeyId, ModuleCode, LicenseAssignmentId, ServiceAccountId | End of M0 |
| F01 | JWT claim schema (sub, tid, tids, jti, amr, context_node_id) and EventEnvelope shape | End of M0 |
| F05 | Licensing module code string — globally unique, immutable once created (BR-LICN-001) | End of M1 |
8. Service readiness levels
| Level | Description | Target |
|---|---|---|
| L2 | Core auth flows, licensing catalogue, manual failover | M0 |
| L3 | MFA, OIDC broker, SLO tracking, contract tests, event schema gate | M1 |
| L4 | SAML, adaptive MFA, chaos-tested broker failover, full HIPAA/GDPR sign-off | M3 |
9. Architecture diagram
10. Key design decisions
- Identity is separate from tenant. One
Useraggregate, many tenant memberships. Membership and RBAC live in tenant-service. - JWT claims are identity-only (
sub,tid,tids,jti,amr,context_node_id). Roles, permissions, effective licenses are fetched on demand (GET /identity/me/access-context) or resolved server-side via tenant-serviceevaluate(). - Licensing co-located with identity. Licenses gate which modules a provider can use at a node. Placing resolution alongside authentication lets edge (Kong) and downstream services check one trust boundary.
- Device binding lives here, encryption derivation in consumers. Identity issues short-lived device certificates; content and chart services derive keys from the binding event.
- Provider abstraction. Auth backends are pluggable (
in_house,keycloak_broker,external_oidc_only). Ghasi JWTs are always re-minted to preservesub/tid/amrstability (F01/F03). - Conservative argon2id (
m=64MiB, t=3, p=1) — resist GPU cracking, keep login latency under 100 ms on target hardware. - Effective license resolution walks ancestor chain at request time (not materialized). Cache 5 min; event-driven invalidation within 30 s (FR-LIC-EFF-005).
is_always_onlicenses cannot be suspended or terminated (BR-LIC-003) — guaranteesehr.core,platform.iam,platform.auditbaseline.
11. Identity backend provider abstraction
| Kind | Role | When to use |
|---|---|---|
in_house (native) | Postgres-backed credentials with RLS, local JWT sign via KMS | Default; full control, offline device binding, no third-party user store |
keycloak_broker | Keycloak realm(s) hold users; identity-service brokers token exchange and re-mints Ghasi JWT | Enterprise customers with central IAM team |
external_oidc_only (Firebase / Okta / Cognito) | OIDC RP against vendor; JIT to ExternalIdentity | Mobile-first, SaaS preference, existing enterprise IdP |
saml_sp | SAML 2.0 SP with encrypted assertions, multiple IdPs per tenant | Org mandates SSO; directory sync via SCIM owned by tenant-service |
All paths re-mint a Ghasi JWT to preserve freeze F01/F03 claim shape.
12. Observability dimensions
Every AuthN outcome is tagged with identity.provider.kind, identity.provider.vendor, identity.upstream.issuer, identity.license.decision — low-cardinality labels for SLO dashboards by backend and by license outcome.
13. Source reconciliation
This service merges three legacy modules. Key decisions:
| Concern | Decision |
|---|---|
iam user + session + MFA + OIDC | Kept as core authentication core. |
access-policy | Moved to tenant-service — this service no longer owns roles, assignments, policies, or evaluate(). identity-service only aggregates access context for client UX (delegates to tenant-service for role data). |
licensing module catalogue + assignments + resolver | Merged into identity-service (co-located with authentication at the trust boundary) rather than kept as a separate service. Justified by: (a) both are cross-tenant platform concerns; (b) license checks happen on every authenticated request; (c) splitting increased service count without changing boundaries. |
| Legacy FR prefixes | Mapped: FR-IAM-* → FR-IDENT-AUTH-*; FR-LICN-* → FR-IDENT-LIC-*; legacy FR-ACPOL-* removed from this service (migrated to FR-TENANT-ACC-*). Legacy refs retained in traceability tables and user-story "Legacy ref" columns. |
| Event subject prefix | identity.*.v1 (e.g., identity.user.registered.v1, identity.license.assignment.status_changed.v1). Legacy com.ghasi-ehr.iam.* and licensing.* retained during migration as dual-publish aliases. See MIGRATION_PLAN.md. |
14. Related specifications
- DOMAIN_MODEL — aggregates, invariants, events.
- API_CONTRACTS — REST surface.
- EVENT_SCHEMAS — produced + consumed.
- DATA_MODEL — Postgres schema + RLS.
- SECURITY_MODEL — trust boundaries, encryption classes.
- MIGRATION_PLAN — legacy iam/access-policy/licensing consolidation plan.
- Cross-service: tenant-service/SERVICE_OVERVIEW for authorization counterpart.