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
| Area | What Identity Owns |
|---|---|
| Credentials | Password hashes (argon2id), WebAuthn public keys, magic-link tokens |
| Sessions | Access JWT issuance, rotating refresh tokens, session revocation |
| Devices | Device registration, fingerprinting, public key storage, offline binding certificates |
| MFA | TOTP enrollment, WebAuthn as second factor, SMS (deprecated for sensitive scopes), recovery codes |
| API Keys | Tenant-scoped API key issuance, rotation, revocation, scope management |
| Federated Identity | OIDC and SAML integration via ACL, JIT user provisioning from SSO callbacks |
| Password Policy | Complexity enforcement, rotation reminders, breach-list checking |
| Account Lifecycle | Registration, email verification, account locking/unlocking, GDPR erasure participation |
| JWKS | Publishing /.well-known/jwks.json for all downstream services |
4. Non-Responsibilities
| Area | Owner | Why Not Identity |
|---|---|---|
| Authorization (RBAC/ABAC) | tenant-service | Identity authenticates; tenant-service authorizes. Separation prevents circular dependencies. |
| User profiles (name, avatar, preferences) | tenant-service | Profile is an organizational concern, not an authentication one. |
| Org membership | tenant-service | A user may belong to many tenants; membership is a tenant-side aggregate. |
| Notification delivery | notification-service | Identity emits events; notification-service handles channels. |
| Audit log storage | analytics-service / audit sink | Identity produces audit events; storage is centralized. |
5. Dependencies
5.1 Upstream Dependencies
| Dependency | Pattern | Purpose |
|---|---|---|
| External OIDC providers (Google, Microsoft, custom) | ACL | Federated login; Identity normalizes external claims into internal User + ExternalIdentity |
| External SAML 2.0 IdPs | ACL | Enterprise SSO; Identity parses SAML assertions and maps to internal model |
| KMS (AWS KMS / Vault) | Infrastructure | JWT signing key storage, device certificate signing, API key hashing secrets |
5.2 Downstream Consumers
| Consumer | Pattern | What They Consume |
|---|---|---|
| Every service | CF (Conformist) for JWT claims | All 18 other services validate JWTs issued by identity-service; they conform to the claim schema |
| tenant-service | Event consumer | Consumes identity.user.registered.v1 to provision membership records |
| notification-service | Event consumer | Consumes password-reset, email-verification events to send emails |
| sync-service | Event consumer | Consumes identity.device.bound_for_offline.v1 for device sync registration |
| content-service | Event consumer | Consumes device binding events for bundle encryption key derivation |
5.3 Events Consumed by Identity
| Event | Producer | Purpose |
|---|---|---|
tenant.org.user_invited.v1 | tenant-service | Provision a shadow user in pending_verification status for invited users who don't yet have an account |
gdpr.subject_request.received.v1 | platform (cross-cutting) | Participate in GDPR erasure saga — anonymize/delete identity data for the subject |
6. Slice Involvement
| Slice | Scope | Milestone |
|---|---|---|
| S0 — Foundation | Basic email+password auth, session management (JWT + refresh), device registration, JWKS endpoint | M0 |
| S1 — Offline & Integration | Device binding for offline (public key cert issuance), API key management | M1 |
| S4 — Enterprise & Security | SAML 2.0 SSO, adaptive MFA (risk-based challenge), advanced lockout policies | M3 |
7. Architectural Freeze Points
| Freeze | What Is Frozen | Frozen By |
|---|---|---|
| F03 | Branded value objects: UserId, TenantId, DeviceId, SessionId, APIKeyId — shape, serialization, and generation algorithm | End of M0 |
| F01 | EventEnvelope schema — all events must conform to the envelope defined in 04 Event-Driven | End of M0 |
After freeze, changes to these require an architectural review and a new version with dual-publish window.
8. Service Readiness Levels
| Level | Description | Target Milestone |
|---|---|---|
| L2 | Core auth flows operational, basic monitoring, manual failover | M0 |
| L3 | SSO, MFA, automated failover, contract tests, SLO tracking | M1 |
| L4 | Full security hardening, adaptive MFA, SAML, chaos-tested, production-grade | M3 |
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
-
Identity is separate from Tenant. A single
Useraggregate exists once, even when the person belongs to multiple tenants. Membership is a tenant-service concern. This is the canonical multi-tenant identity pattern. -
JWT claims include
tid(active tenant) andtids(available tenants). Switching tenant context is a session re-mint, not a new login. -
Device binding lives in Identity, not Content. Devices are an identity concern; content-service consumes the binding event to derive encryption keys.
-
Offline device certificates are short-lived and revocable. Revocation propagates via sync within minutes when online; offline devices honor certificate expiry.
-
argon2id parameters are conservative.
m=64MiB, t=3, p=1chosen to resist GPU-based cracking while keeping login latency under 100ms on target hardware. -
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:
| Kind | Role | When to use |
|---|---|---|
| In-house (native) | Default adapter: Postgres-backed users, credentials, sessions, devices, MFA; local JWT signing via KMS | Full control, offline device binding, RLS-backed credentials, no third-party user store |
| Keycloak | Keycloak realm(s) hold users/groups; identity-service performs token exchange or broker callback normalization, then mints platform JWTs + refresh sessions per existing rules | Enterprise customers already running Keycloak; central IAM team owns realms |
| Firebase Auth / Okta / AWS Cognito | Placeholder adapters in the spec: OIDC upstream using vendor discovery + JWKS; same JIT + ExternalIdentity linking model as generic OIDC | SaaS 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 participation | Organizations 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+isstoExternalIdentity; 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:
| Vendor | Discovery / notes |
|---|---|
| Firebase Auth | Google OIDC; aud is Firebase project; validate via Google JWKS; map user_id / sub |
| Okta | Okta issuer URI; groups/claims mapped via tenant attribute mappings |
| AWS Cognito | Cognito 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 viasso_providersrows (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)
| Scope | What is configured |
|---|---|
| Deployment | Default IdentityBackendKind: in_house | keycloak_broker | external_oidc_only (e.g. all users via external OIDC) |
| Tenant | SSO 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.
14. Related specifications
- APPLICATION_LOGIC — ports and handler wiring.
- DOMAIN_MODEL —
ExternalIdentityprovider vocabulary. - MIGRATION_PLAN — moving tenants between backends.
- 03 identity-service — condensed architecture entry.