Skip to main content

Overview

:::info Source Sourced from services/identity-service/SERVICE_OVERVIEW.md in the documentation repo. :::

Companion: 03 identity-service · 02 DDD · 13 Security

1. Purpose

The identity-service is the authoritative source of who a principal is within the Ghasi-edTech platform. It owns the full lifecycle of authentication credentials, sessions, devices, MFA factors, API keys, and federated identity (OIDC/SAML). It issues JWTs consumed by every other service.

2. Bounded Context

Identity & Access — classified as Generic (commodity domain). This context could theoretically be replaced by a SaaS identity provider, but is kept in-house for:

  • Tenant isolation guarantees — RLS-backed credential storage with no third-party data path.
  • Offline device binding — tight coupling between device public keys and content encryption keys.
  • Cost control — at scale, SaaS per-MAU pricing exceeds self-hosted marginal cost.

3. Responsibilities

AreaWhat Identity Owns
CredentialsPassword hashes (argon2id), WebAuthn public keys, magic-link tokens
SessionsAccess JWT issuance, rotating refresh tokens, session revocation
DevicesDevice registration, fingerprinting, public key storage, offline binding certificates
MFATOTP enrollment, WebAuthn as second factor, SMS (deprecated for sensitive scopes), recovery codes
API KeysTenant-scoped API key issuance, rotation, revocation, scope management
Federated IdentityOIDC and SAML integration via ACL, JIT user provisioning from SSO callbacks
Password PolicyComplexity enforcement, rotation reminders, breach-list checking
Account LifecycleRegistration, email verification, account locking/unlocking, GDPR erasure participation
JWKSPublishing /.well-known/jwks.json for all downstream services

4. Non-Responsibilities

AreaOwnerWhy Not Identity
Authorization (RBAC/ABAC)tenant-serviceIdentity authenticates; tenant-service authorizes. Separation prevents circular dependencies.
User profiles (name, avatar, preferences)tenant-serviceProfile is an organizational concern, not an authentication one.
Org membershiptenant-serviceA user may belong to many tenants; membership is a tenant-side aggregate.
Notification deliverynotification-serviceIdentity emits events; notification-service handles channels.
Audit log storageanalytics-service / audit sinkIdentity produces audit events; storage is centralized.

5. Dependencies

5.1 Upstream Dependencies

DependencyPatternPurpose
External OIDC providers (Google, Microsoft, custom)ACLFederated login; Identity normalizes external claims into internal User + ExternalIdentity
External SAML 2.0 IdPsACLEnterprise SSO; Identity parses SAML assertions and maps to internal model
KMS (AWS KMS / Vault)InfrastructureJWT signing key storage, device certificate signing, API key hashing secrets

5.2 Downstream Consumers

ConsumerPatternWhat They Consume
Every serviceCF (Conformist) for JWT claimsAll 18 other services validate JWTs issued by identity-service; they conform to the claim schema
tenant-serviceEvent consumerConsumes identity.user.registered.v1 to provision membership records
notification-serviceEvent consumerConsumes password-reset, email-verification events to send emails
sync-serviceEvent consumerConsumes identity.device.bound_for_offline.v1 for device sync registration
content-serviceEvent consumerConsumes device binding events for bundle encryption key derivation

5.3 Events Consumed by Identity

EventProducerPurpose
tenant.org.user_invited.v1tenant-serviceProvision a shadow user in pending_verification status for invited users who don't yet have an account
gdpr.subject_request.received.v1platform (cross-cutting)Participate in GDPR erasure saga — anonymize/delete identity data for the subject

6. Slice Involvement

SliceScopeMilestone
S0 — FoundationBasic email+password auth, session management (JWT + refresh), device registration, JWKS endpointM0
S1 — Offline & IntegrationDevice binding for offline (public key cert issuance), API key managementM1
S4 — Enterprise & SecuritySAML 2.0 SSO, adaptive MFA (risk-based challenge), advanced lockout policiesM3

7. Architectural Freeze Points

FreezeWhat Is FrozenFrozen By
F03Branded value objects: UserId, TenantId, DeviceId, SessionId, APIKeyId — shape, serialization, and generation algorithmEnd of M0
F01EventEnvelope schema — all events must conform to the envelope defined in 04 Event-DrivenEnd of M0

After freeze, changes to these require an architectural review and a new version with dual-publish window.

8. Service Readiness Levels

LevelDescriptionTarget Milestone
L2Core auth flows operational, basic monitoring, manual failoverM0
L3SSO, MFA, automated failover, contract tests, SLO trackingM1
L4Full security hardening, adaptive MFA, SAML, chaos-tested, production-gradeM3

9. Architecture Diagram

┌───────────────────────────────────────────────┐
│ API Gateway / Edge │
└──────────────────┬────────────────────────────┘

┌──────────────────▼────────────────────────────┐
│ identity-service (NestJS) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Auth │ │ Device │ │ API Key │ │
│ │ Module │ │ Module │ │ Module │ │
│ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
│ │ │ │ │
│ ┌────▼─────────────▼──────────────▼──────┐ │
│ │ Domain Layer (pure TS) │ │
│ │ User · Credential · Session · Device │ │
│ │ MFAFactor · APIKey · ExternalIdentity │ │
│ └────────────────┬───────────────────────┘ │
│ │ │
│ ┌────────────────▼───────────────────────┐ │
│ │ Infrastructure Layer │ │
│ │ Postgres (RLS) · NATS · KMS · Redis │ │
│ │ argon2id · jose · openid-client │ │
│ │ samlify · OutboxRelay │ │
│ └────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
│ │ │
┌─────────▼──┐ ┌────────▼───┐ ┌─────▼──────┐
│ Postgres │ │ NATS │ │ KMS │
│ (schema: │ │ JetStream │ │ (signing │
│ identity) │ │ │ │ keys) │
└────────────┘ └────────────┘ └────────────┘

10. Key Design Decisions

  1. Identity is separate from Tenant. A single User aggregate exists once, even when the person belongs to multiple tenants. Membership is a tenant-service concern. This is the canonical multi-tenant identity pattern.

  2. JWT claims include tid (active tenant) and tids (available tenants). Switching tenant context is a session re-mint, not a new login.

  3. Device binding lives in Identity, not Content. Devices are an identity concern; content-service consumes the binding event to derive encryption keys.

  4. Offline device certificates are short-lived and revocable. Revocation propagates via sync within minutes when online; offline devices honor certificate expiry.

  5. argon2id parameters are conservative. m=64MiB, t=3, p=1 chosen to resist GPU-based cracking while keeping login latency under 100ms on target hardware.

  6. Identity backends are pluggable behind stable application ports. The authoritative platform contract is still identity-service issuing (or delegating and re-issuing) access JWTs with the frozen claim schema; credential storage and upstream IdP negotiation are implemented by provider adapters (see below). This lets a deployment choose in-house storage, a Keycloak broker, or external OIDC/SAML IdPs without forking the domain model.

11. Identity backend & provider abstraction

Identity-service exposes application ports for authentication, session lifecycle, federated login, and token issuance. Implementations are grouped into:

KindRoleWhen to use
In-house (native)Default adapter: Postgres-backed users, credentials, sessions, devices, MFA; local JWT signing via KMSFull control, offline device binding, RLS-backed credentials, no third-party user store
KeycloakKeycloak realm(s) hold users/groups; identity-service performs token exchange or broker callback normalization, then mints platform JWTs + refresh sessions per existing rulesEnterprise customers already running Keycloak; central IAM team owns realms
Firebase Auth / Okta / AWS CognitoPlaceholder adapters in the spec: OIDC upstream using vendor discovery + JWKS; same JIT + ExternalIdentity linking model as generic OIDCSaaS IdP preference, mobile-first (Firebase), enterprise Okta/Cognito standardization
External enterprise IdP (BYO)SAML 2.0 SP and OIDC RP flows against customer IdPs (Azure AD, Okta, Ping, ADFS, etc.); optional directory sync (SCIM) owned by tenant-service with identity participationOrganizations that mandate SSO-only and federate an existing user directory

Non-goals: Authorization (RBAC/ABAC) remains in tenant-service. Provider adapters must not bypass outbox events or tenant isolation rules.

11.1 In-house (native) provider

The current implementation path is specified as the in-house provider: password hashing, WebAuthn, magic links, refresh rotation, device registration, offline bind certificates, API keys — all persisted under the identity schema with RLS. Application handlers depend on ports (PasswordHasher, TokenSigner, etc.) implemented by this adapter. Extracting code in the application repo means: domain + application use cases stay unchanged; infrastructure modules group under infrastructure/providers/in-house/ (or equivalent) behind IdentityAuthenticationProvider / CredentialStore ports.

11.2 Keycloak provider

Keycloak acts as an OIDC/SAML broker or primary user store. Identity-service:

  • Registers confidential clients per tenant/realm (config held in tenant-service SSO config, validated by identity-service).
  • Completes OAuth2/OIDC code flow against Keycloak; validates tokens using Keycloak realm JWKS.
  • Maps Keycloak sub + iss to ExternalIdentity; JIT provisions users identically to generic OIDC.
  • Optionally uses Token Exchange (RFC 8693) where platform policy requires a second hop from Keycloak-issued tokens to Ghasi session tokens.
  • Emits the same events (identity.user.logged_in.v1, etc.) so downstream services remain unaware of Keycloak.

Operational: Keycloak is deployed alongside or customer-managed; identity-service egress allowlists Keycloak base URLs per tenant.

11.3 Commercial IdP placeholders (Firebase Auth, Okta, Cognito)

These adapters are OIDC-first with vendor-specific discovery URLs and claim normalization:

VendorDiscovery / notes
Firebase AuthGoogle OIDC; aud is Firebase project; validate via Google JWKS; map user_id / sub
OktaOkta issuer URI; groups/claims mapped via tenant attribute mappings
AWS CognitoCognito hosted UI issuer; cognito:username + sub mapping

Delivery status: Spec and ports are defined first; full implementation may be phased per slice. Until GA of a given adapter, deployment configuration disables that adapter’s code paths and automated tests skip the vendor-specific suite.

11.4 External enterprise IdP & directory federation (SSO)

For organizations with existing IdPs and directories:

  • SSO: SAML 2.0 and OIDC flows remain the primary integration (see APPLICATION_LOGIC — LoginWithSSO, LoginWithSAML). Multiple IdPs per tenant are supported via sso_providers rows (tenant-service) keyed by tenant + protocol + entity ID / issuer.
  • Forced SSO / domain routing: Email domain → IdP binding is evaluated in application policy before password login is offered.
  • Directory sync: Optional SCIM 2.0 (or HR-driven provisioning) is specified in tenant-service; identity-service receives tenant.org.user_invited.v1 / SCIM-derived events and continues to create shadow users and link external identities as today.
  • Trust & security: PKCE, state/nonce, encrypted SAML assertions, and issuer allowlists are mandatory; JIT provisioning respects tenant membership rules.

11.5 JWT issuance model

Downstream services always validate Ghasi-issued JWTs from /.well-known/jwks.json unless explicitly configured for a rare delegated mode (not default). In-house and Keycloak paths re-mint Ghasi JWTs after successful upstream authentication so sub, tid, tids, and amr remain stable per freeze F03.

12. Provider selection (deployment vs tenant)

ScopeWhat is configured
DeploymentDefault IdentityBackendKind: in_house | keycloak_broker | external_oidc_only (e.g. all users via external OIDC)
TenantSSO IdP metadata (SAML/OIDC), Keycloak realm/client IDs, attribute maps, domain allowlists

Tenant-level SSO configuration (US-10) remains in tenant-service; identity-service validates and executes technical flows.

13. Observability dimensions

Every authentication outcome is tagged with identity.provider.kind, identity.provider.vendor, and identity.upstream.issuer (low-cardinality) for dashboards and SLOs by backend.