Skip to main content

0001. ThunderID as the sole identity provider

Status: Accepted

Context​

OpenSchool needs authentication, JWT issuance, and per-user role assignment (admin/teacher/student/parent). The project initially integrated with Asgardeo, then migrated to ThunderID. Rather than build OpenSchool's own credential store, both integrations were built behind a provider-neutral seam (internal/identity.Provider: CreateUser/UpdateUser/DeleteUser/AssignRole), so the concrete client could be swapped without touching the services that call it.

Decision​

ThunderID is the system of record for login credentials and the four base roles. The backend never stores a primary login password. Token validation happens against ThunderID's JWKS endpoint (internal/middleware/auth.go); provisioning (account create/update/ delete, role assignment) happens through internal/thunderid.Client, which implements identity.Provider.

Consequences​

  • A running ThunderID instance is a hard dependency. There is no degraded/offline authentication mode - if ThunderID is unreachable, sign-in and account provisioning both fail.
  • Swapping identity providers again is possible but not free. A new provider needs its own identity.Provider implementation plus a migration plan for existing local users rows (which store no provider-specific data beyond the shared UUID and role).
  • Two prior production incidents were caused by hand-typed ThunderID attribute/role-name strings (in env vars and request payloads) that have to match out-of-repo ThunderID console configuration, failing silently at runtime with a generic error rather than at build/test time. This is the direct reason ADR 0002 chose not to add new IDP roles for the position hierarchy - every new IDP-side string is a repeat of this exact risk. See the project's audit.md findings on AssignRole failure handling for where this fragility has caused real bugs.