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 requireAuth — principal.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