The Atlas MikeOSS.Azure's docs, bound to the code — and to the migration that built it
108 documents

First hour with the repo

Orientation: what MikeOSS.Azure is, the invariants that keep the fork mergeable, the code-verified architecture, and where the migration record lives.

4 · The auth provider boundary

requireAuth (backend/src/middleware/auth.ts:19-68) dispatches on process.env.AUTH_PROVIDER (default supabase), stashes a provider-neutral AuthPrincipal into res.locals, upserts the user profile, then delegates to tenantAccess. Roles are not resolved in requireAuthprincipal.roles stays [] until tenantAccess fills it (entra mode only).

Provider File Validation Groups / roles
entra providers/entra.ts:97-247 RS256 only; JWKS by kid (5-min cache); issuer (v1 and v2 accepted); audience (<guid> or api://<guid>); tid match; exp/nbf; oid→userId groups from claims.groups; overage → empty [] (see §11)
local providers/local.ts:31-97 HS256 over JWT_SECRET, timingSafeEqual; sub+exp required trusts token's own groups/roles arrays
supabase providers/supabase.ts:4-32 delegates to admin.auth.getUser(token) — no local signature check always empty groups/roles

tenantAccess (backend/src/middleware/tenantAccess.ts) is Entra-only — it next()s immediately for other providers (:35-39). In entra mode it requires principal.tenantId, looks up the tenants row, auto-onboards on first sign-in when tenant-onboarding-mode === "auto" (upsert with ignoreDuplicates to survive the first-request race, :69-78), then resolves principal.groups → roles via resolveRoles (backend/src/lib/auth/roles.ts:25-58): admin-group → ["TenantAdmin","Member"]; an empty member-group list means "any tenant member" → ["Member"]; otherwise match-or-deny (GROUP_NOT_WHITELISTED).

flowchart LR
  T["Bearer token"] --> D{AUTH_PROVIDER}
  D -->|entra| E["RS256 + JWKS + issuer/aud/tid/exp\nentra.ts"]
  D -->|local| L["HS256 + JWT_SECRET\nlocal.ts"]
  D -->|supabase| S["admin.auth.getUser\nsupabase.ts"]
  E --> P["AuthPrincipal"]
  L --> P
  S --> P
  P --> TA{entra?}
  TA -->|yes| TEN["tenantAccess: tenant row + group→role\ntenantAccess.ts · roles.ts"]
  TA -->|no| H["handler (roles = [])"]
  TEN --> H