Skip to main content

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

AreaWhat identity-service owns
Credentialsargon2id password hashing, WebAuthn public keys, magic links, breach-list checks
SessionsShort-lived platform JWT (15 min), rotating refresh tokens (8h), revocation, concurrent session caps
MFATOTP enrollment, WebAuthn second factor, recovery codes, adaptive challenge (slice S4)
DevicesRegistration, fingerprinting, public-key storage, offline-binding certificates (short-lived, revocable)
API keysTenant-scoped API key issuance, rotation, revocation, scoped permissions for M2M
Service accountsKeycloak confidential clients, client-credentials flow, rotation, revocation
Federated identityOIDC (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 lifecycleRegistration, email verification, lock/unlock, suspend/reactivate, soft-delete, GDPR erasure participation
Access context aggregationGET /identity/me/access-context composes roles (tenant-service) + memberships (tenant-service hierarchy) + effective licenses (internal licensing module) for UI gating
Module catalogueStable code registry (ehr.core, diag.laboratory, clinical.immunizations, ...), dependency graph, is_always_on flags
License assignmentsNode-scoped exact / inherit-down licenses, status transitions (trial / active / suspended / expired / terminated), constraints (seats, expiry, usage caps), append-only history
Effective license resolutionWalk hierarchy ancestor chain; overlay with deeper-wins rule; cache effective set per (tenantId, providerId, nodeId) with 5 min TTL

4. Non-responsibilities

AreaOwnerWhy not identity-service
Authorization policy (RBAC/ABAC)tenant-serviceAuthN vs AuthZ separation; prevents circular dependencies.
User profile & membershiptenant-serviceA user may belong to many tenants; membership is a tenant aggregate.
Organizational hierarchytenant-serviceHierarchy nodes (facility / department / ward) live with tenant governance.
Tenant lifecycletenant-serviceIdentity references tenantId but does not create tenants.
Notification deliverycommunication-serviceIdentity emits events; communication-service sends email/SMS/push.
Audit storageaudit-serviceIdentity produces audit events; audit-service centralizes retention.
Cross-tenant admin console UI backendplatform-admin-serviceIdentity exposes primitives; platform-admin-service consumes them.

5. Upstream & downstream dependencies

5.1 Upstream

DependencyPatternPurpose
Keycloak 24 (realm-per-tenant or shared)BrokerOIDC/SAML authentication front-end, user credential store option
External OIDC IdPs (Google, Firebase, Okta, Cognito, customer Azure AD)ACLFederated login, JIT provisioning
External SAML 2.0 IdPsACLEnterprise SSO with encrypted assertions
KMS (AWS KMS / HashiCorp Vault)InfrastructureJWT signing keys, device certificate signing, API key HMAC secrets
tenant-serviceEvent consumer (tenant.activated, tenant.suspended, tenant.terminated, tenant.user.invited)Provision shadow users, invalidate sessions on suspension, deactivate on termination
config-servicePullEnvironment / per-tenant runtime config (session timeouts, MFA policy defaults)

5.2 Downstream consumers

ConsumerPatternWhat they consume
All 26 other servicesConformist for JWT claimsValidate JWTs via JWKS; read sub, tid, tids, amr, context_node_id
tenant-serviceEvent consumeridentity.user.registered.v1, identity.user.suspended.v1 for membership bookkeeping
communication-serviceEvent consumerPassword reset, email verification, MFA enrollment completed
patient-portal-service, virtual-care-serviceJWT consumerPatient identity assertions, device trust
sync/offline path in patient-chart-service, document-serviceEvent consumeridentity.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 edgePull (JWKS)Validate JWTs at the gateway

6. Slice involvement

SliceScopeMilestone
S0 — FoundationEmail+password, session JWT + refresh, device registration, JWKS, service accounts, module catalogue seed, always-on licensesM0
S1 — Offline & licensingDevice-binding certificates for offline, API key management, effective-license resolver with inheritance, license assignment lifecycleM1
S2 — Federated identityOIDC generic + Keycloak broker, JIT provisioningM2
S4 — Enterprise & securitySAML 2.0, adaptive MFA (risk-based), SCIM participation, session freeze, break-glass enablementM3

7. Architectural freeze points

FreezeWhat is frozenFrozen by
F03Branded value objects: UserId, TenantId, DeviceId, SessionId, APIKeyId, ModuleCode, LicenseAssignmentId, ServiceAccountIdEnd of M0
F01JWT claim schema (sub, tid, tids, jti, amr, context_node_id) and EventEnvelope shapeEnd of M0
F05Licensing module code string — globally unique, immutable once created (BR-LICN-001)End of M1

8. Service readiness levels

LevelDescriptionTarget
L2Core auth flows, licensing catalogue, manual failoverM0
L3MFA, OIDC broker, SLO tracking, contract tests, event schema gateM1
L4SAML, adaptive MFA, chaos-tested broker failover, full HIPAA/GDPR sign-offM3

9. Architecture diagram

10. Key design decisions

  1. Identity is separate from tenant. One User aggregate, many tenant memberships. Membership and RBAC live in tenant-service.
  2. 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-service evaluate().
  3. 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.
  4. Device binding lives here, encryption derivation in consumers. Identity issues short-lived device certificates; content and chart services derive keys from the binding event.
  5. Provider abstraction. Auth backends are pluggable (in_house, keycloak_broker, external_oidc_only). Ghasi JWTs are always re-minted to preserve sub/tid/amr stability (F01/F03).
  6. Conservative argon2id (m=64MiB, t=3, p=1) — resist GPU cracking, keep login latency under 100 ms on target hardware.
  7. Effective license resolution walks ancestor chain at request time (not materialized). Cache 5 min; event-driven invalidation within 30 s (FR-LIC-EFF-005).
  8. is_always_on licenses cannot be suspended or terminated (BR-LIC-003) — guarantees ehr.core, platform.iam, platform.audit baseline.

11. Identity backend provider abstraction

KindRoleWhen to use
in_house (native)Postgres-backed credentials with RLS, local JWT sign via KMSDefault; full control, offline device binding, no third-party user store
keycloak_brokerKeycloak realm(s) hold users; identity-service brokers token exchange and re-mints Ghasi JWTEnterprise customers with central IAM team
external_oidc_only (Firebase / Okta / Cognito)OIDC RP against vendor; JIT to ExternalIdentityMobile-first, SaaS preference, existing enterprise IdP
saml_spSAML 2.0 SP with encrypted assertions, multiple IdPs per tenantOrg 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:

ConcernDecision
iam user + session + MFA + OIDCKept as core authentication core.
access-policyMoved 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 + resolverMerged 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 prefixesMapped: 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 prefixidentity.*.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.