The three-mode auth boundary
Follow one request from token to authorized handler across all three providers, then into the Entra-only tenant/role gate and the data edge — doc to code and back.
backend/src/middleware/auth.ts69 lines
Outline 1 symbols
- requireAuth function export
1import { Request, Response, NextFunction } from "express";
2import { validateSupabaseToken } from "../lib/auth/providers/supabase.js";
3import { validateLocalToken } from "../lib/auth/providers/local.js";
4import { validateEntraToken } from "../lib/auth/providers/entra.js";
5import { tenantAccess } from "./tenantAccess.js";
6import { upsertUserProfile } from "../lib/userSettings.js";
7
8// Upstream divergence (sync-log: 3a10943): upstream added app-level MFA
9// enforcement here (enforceLoginMfaIfEnabled / requireMfaIfEnrolled) built
10// on Supabase Auth's MFA APIs (admin.auth.mfa.getAuthenticatorAssuranceLevel,
11// auth.getUser factor listings). Dev's auth is provider-pluggable
12// (supabase | local | entra) with Entra as the production provider; those
13// Supabase Auth MFA primitives do not exist for Entra, where MFA/step-up is
14// enforced by the identity provider (Conditional Access), not application
15// code. NOT adopted — do not re-introduce Supabase-session MFA checks in
16// this middleware. If app-level step-up is ever needed, it must be designed
17// per-provider behind lib/auth/providers/.
18
19export async function requireAuth(
20 req: Request,
21 res: Response,
22 next: NextFunction,
23): Promise<void> {
24 const auth = req.headers.authorization ?? "";
25 if (!auth.startsWith("Bearer ")) {
26 res.status(401).json({ detail: "Missing or invalid Authorization header" });
27 return;
28 }
29 const token = auth.slice(7).trim();
30
31 const provider = process.env.AUTH_PROVIDER ?? "supabase";
32
33 let result;
34 if (provider === "supabase") {
35 result = await validateSupabaseToken(token);
36 } else if (provider === "local") {
37 result = await validateLocalToken(token);
38 } else if (provider === "entra") {
39 result = await validateEntraToken(token);
40 } else {
41 res.status(500).json({ detail: `Auth provider '${provider}' is not yet implemented` });
42 return;
43 }
44
45 if (!result.ok) {
46 res.status(result.status).json({ detail: result.detail });
47 return;
48 }
49
50 res.locals.userId = result.principal.userId;
51 res.locals.userEmail = result.principal.email;
52 res.locals.token = token;
53 res.locals.principal = result.principal;
54
55 try {
56 await upsertUserProfile(
57 result.principal.userId,
58 result.principal.email,
59 result.principal.displayName,
60 );
61 } catch (error) {
62 const detail = error instanceof Error ? error.message : "Unable to initialize user profile";
63 res.status(500).json({ detail });
64 return;
65 }
66
67 await tenantAccess(req, res, next);
68}
69