Skip to content

IAM And Profile Service Design

Decision

Use iam-service, not auth-service, as the Go platform boundary.

The legacy code has an AuthService class, but the target service owns more than login/logout. It is the identity and access-management boundary: accounts, credentials, sessions, refresh tokens, JWT/JWKS, roles, permissions, organization membership used for access control, invitations, login audit, and security events.

profile-service is separate because profile data is education-context data, not authentication state. It owns display/profile data for students, teachers, parents, and staff, plus public profile views. It references IAM accounts by account_id and does not handle password, token, session, role, or permission logic.

Boundaries

iam-service owns:

  • account identity and credential state
  • password verification and password-change/reset workflows
  • access-token and refresh-token minting
  • refresh-token hash/revoke/rotation state
  • JWKS and gateway-compatible JWT claims
  • roles, permissions, access-control memberships, and invites
  • login/security audit events

iam-service must not own avatar, display profile, learning progress, exam result, course progress, wallet, subscription, payment, or usage quota state.

profile-service owns:

  • public profile and display fields
  • student, teacher, parent, and staff profile sections
  • avatar/profile-contact metadata
  • teacher KYC storage, self submit/read, owner-side admin review, and safe redaction
  • parent-child relationship read models where profile UI needs them
  • internal profile snapshots for service-to-service hydration

profile-service must not own passwords, refresh tokens, sessions, JWT signing, roles, permissions, or login/register flows.

Runtime And Data

Local development uses service-owned databases:

  • hoctapaz_iam_db
  • hoctapaz_profile_db

The shared local Postgres container creates these databases through deploy/postgres/init. Service code must use the service-owned database only; cross-service references use IDs and API/event contracts, not foreign keys.

Offline/local Kubernetes manifests for the original two-service slice live under deployments/k8s/local/. The active platform manifests live under deploy/k8s/services, and Helm renders the same services from deploy/helm/hoctapaz.

Flow Summary

Login/register/refresh:

  1. API Gateway receives /api/auth/*.
  2. During rehearsal, exact native carve-outs forward to iam-service.
  3. iam-service validates credentials or refresh-token state and returns the legacy-compatible session envelope.
  4. Gateway/frontend keep using the existing access-token storage contract until public cutover is explicitly approved.

Profile update:

  1. Gateway authenticates the actor with IAM-compatible JWT claims.
  2. Profile routes forward to profile-service only after response-shape adapters are explicit.
  3. profile-service persists profile data by account_id without reading the IAM database.

Service verification:

  1. Services verify JWTs through gateway headers or IAM JWKS public keys when RS256 signing is configured.
  2. Services do not call IAM on every request unless a dynamic permission/session check is required.
  3. Tenant and ownership checks use owner-service APIs or synchronized read models, never cross-database joins.

Current Implementation Status

  • services/iam-service implements the native auth/session foundation, password change/reset, Google provider-link/admin settings, persisted session records, login audit writes, RS256/JWKS signing when a private key is configured, directory/invite workflows, parent-student permission links, teacher subject scopes, staff permission scopes, migrations, Dockerfile, OpenAPI, docs, and tests.
  • services/profile-service implements profile snapshot/read/update foundations, role-profile tables, internal profile contacts, parent-child profile read models, teacher KYC self submit/read redaction, owner-side admin KYC review, public redaction without role/contact leakage, migrations, Dockerfile, OpenAPI, docs, and tests.
  • IAM/Profile usecase events use noop publishers by default: iam.account.created, iam.account.updated, iam.account.disabled, iam.role.assigned, iam.organization.member_added, profile.created, profile.updated, profile.snapshot.upserted, and profile.teacher_kyc.submitted/profile.teacher_kyc.reviewed.
  • docs/microservices/legacy-user-auth-inventory.md records the legacy source inventory and service split.
  • Default public routes remain conservative; native route tables are rehearsals until browser/runtime parity and rollback evidence are complete.
  • make test-iam-profile-route-rollback validates that the auth/profile non-default route examples can roll back to deploy/gateway/routes.json, where broad auth, admin, and fallback remain legacy_proxy.
  • make test-iam-profile-runtime-proof verifies the current static/render runtime wiring for IAM/Profile Compose, the two-service local K8s manifest, active offline K8s manifests, and Helm bootstrap.
  • IAM_PROFILE_CLEAN_COMPOSE_CONFIRM=clean-runtime make test-iam-profile-clean-compose is the opt-in live clean Compose smoke for IAM/Profile migration completion, schema_migrations rows, Postgres health, service readiness, and IAM JWKS. It preflights host ports 5433, 8081, and 8082 by default; use IAM_PROFILE_HOST_POSTGRES_PORT, IAM_PROFILE_HOST_IAM_PORT, and IAM_PROFILE_HOST_PROFILE_PORT when a local developer stack already owns those ports. If the IAM/Profile service images are already built locally, set IAM_PROFILE_CLEAN_COMPOSE_USE_LOCAL_IMAGES=1 and IAM_PROFILE_REBUILD_MIGRATOR_IMAGE=1 to run the same proof with local images and Docker Compose --no-build.
  • IAM_PROFILE_K8S_LIVE_CONFIRM=local-k8s make test-iam-profile-local-k8s is the opt-in local/offline Kubernetes smoke for the two-service IAM/Profile slice. It applies the local manifest into an isolated namespace, runs temporary IAM/Profile schema migration Jobs, checks schema_migrations, restarts deployments, verifies /healthz, /readyz, and IAM JWKS through kubectl port-forward, then deletes the namespace by default. Build and load hoctapaz/iam-service, hoctapaz/profile-service, and hoctapaz/migrator images into the local cluster first. The smoke refuses non-local contexts by default; IAM_PROFILE_K8S_ALLOW_REMOTE=1 is an explicit override for deliberate remote-cluster testing.

Remaining Risks

  • Password change/reset and Google provider-link parity are implemented in native iam-service, and exact /api/auth/* non-default gateway rehearsals exist. Static rollback rehearsal exists. Default promotion still needs browser/runtime proof, response-shape acceptance, production-like populated DB validation, and explicit approval before any route becomes default.
  • Teacher KYC self submit/read and owner-side admin review are native in profile-service, and exact self-service plus admin KYC gateway rehearsals are available in non-default route tables. Default promotion, admin-service compatibility wrapping, and browser/runtime proof still need dedicated cutover evidence.
  • Current migrations keep TEXT id columns to preserve legacy/backfill and string-id API compatibility, while service-owned PostgreSQL primary keys are promoted to UUID columns in the target IAM/Profile schemas. Production-like target validation, final text-id cleanup, and public route/browser promotion remain tracked in the IAM/Profile UUID cutover design.
  • IAM directory/audit endpoints remain gateway/internal surfaces. Direct management routes use service-local admin authz, while owner/internal routes require X-Internal-Service plus the shared X-Internal-Token; direct public exposure still needs gateway rehearsal, browser proof, and rollback evidence.
  • Profile keeps role/status as IAM-synchronized projection metadata only. These fields must not become profile-owned permission state.
  • IAM/school organization-membership ownership must stay explicit to avoid split-brain tenant access decisions.
  • Backfill and validation reports must run before any default /api/auth/* cutover beyond already rehearsed routes.
  • Static/render runtime wiring is guarded, and clean Compose proof was refreshed on 2026-07-08 after the UUID dual-write migrations with local IAM/Profile images, a rebuilt local migrator image, alternate host ports 55433/18081/18082, and real IAM/Profile health, readiness, per-file migration-record, and JWKS checks for the current IAM 13-file and Profile 10-file migration sets. Local K8s proof was also refreshed on 2026-07-08 against kind-hoctapaz-go after loading hoctapaz/iam-service:local, hoctapaz/profile-service:local, and hoctapaz/migrator:local; it verified migration Jobs, service-owned schema_migrations counts for the same 13/10 migration sets, service /healthz//readyz, and IAM JWKS, then removed the smoke namespace. Current default-route rollback evidence now exists through make test-iam-profile-route-rollback; production-like populated validation, browser proof for promoted routes, UUID primary-key promotion, and default route promotion remain pending.

Go-platform documentation is generated from repository Markdown.