Architecture · 30 min
API Architecture & Security Guide
A complete technical breakdown of the AuthMatrix Express API backend, Express 5 TypeScript handlers, Zero Trust RBAC middleware, and Azure APIM gateway integration.
1. Monorepo Architecture
The AuthMatrix repository decouples presentation, authentication, and REST API microservices into distinct monorepo packages under apps/:
apps/learn-site: Static Astro documentation deployed to GitHub Pages (/AuthMatrix).apps/astro-frontend: SSR application running onhttp://localhost:3000for local OIDC lab authentication (Okta & Entra ID).apps/api-server: Express 5 TypeScript REST API backend running onhttp://localhost:4000enforcing fine-grained RBAC.config/azure-apim-policy.xml: Inbound XML policy for Azure API Management enforcing JWT validation and claim normalization.
2. API Endpoint Inventory
The API server exposes 10 endpoints categorized by Zero Trust privilege requirements:
| Method | Endpoint | Permission | Allowed Roles |
|---|---|---|---|
GET | /api/public | None | Anonymous |
GET | /api/public/health | None | Anonymous |
POST | /api/auth/mock-token | None | Anonymous |
GET | /api/protected/me | Authenticated | All Roles |
GET | /api/protected/users | read:users | Admin, Manager, Developer |
POST | /api/protected/users | write:users | Admin |
DELETE | /api/protected/users/:id | delete:users | Admin |
GET | /api/protected/reports | read:reports | Admin, Manager, Developer, Auditor |
POST | /api/protected/reports | write:reports | Admin, Manager |
PUT | /api/protected/settings | write:settings | Admin |
GET | /api/protected/audit | read:audit | Admin, Auditor |
DELETE | /api/protected/audit | delete:audit | Admin |
POST | /api/protected/jobs | execute:jobs | Admin, Developer |
3. Middleware & Code Implementation
Every incoming request passes through an authentication middleware (requireAuth) followed by fine-grained permission middleware (requirePermission):
// Authentication Middleware — inspects Gateway Headers or Bearer JWT
export const requireAuth = (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
const gatewayUser = req.headers['x-user-id'] as string;
const gatewayRoles = req.headers['x-user-roles'] as string;
const gatewayPermissions = req.headers['x-user-permissions'] as string;
if (gatewayUser) {
req.user = {
id: gatewayUser,
email: `${gatewayUser}@authmatrix.local`,
roles: gatewayRoles ? gatewayRoles.split(',') : [],
permissions: gatewayPermissions ? gatewayPermissions.split(',') : [],
};
return next();
}
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized', message: 'Missing Bearer token' });
}
try {
const token = authHeader.substring(7);
const decoded = jwt.verify(token, JWT_SECRET) as any;
req.user = {
id: decoded.sub,
email: decoded.email,
roles: decoded.roles || [],
permissions: decoded.permissions || [],
};
next();
} catch (err) {
return res.status(401).json({ error: 'Unauthorized', message: 'Invalid token signature' });
}
}; 4. Route Handlers with Code Examples
Example handler for privileged user deprovisioning (DELETE /api/protected/users/:id):
app.delete('/api/protected/users/:id', requireAuth, requirePermission('delete:users'), (req: AuthenticatedRequest, res: Response) => {
const id = parseInt(String(req.params.id), 10);
const idx = USERS_DB.findIndex(u => u.id === id);
if (idx === -1) {
return res.status(404).json({ error: 'Not Found', message: `User ${id} not found` });
}
const removed = USERS_DB.splice(idx, 1)[0];
res.json({
status: 'success',
message: `User '${removed.name}' deprovisioned successfully.`,
operator: req.user?.id,
});
}); 5. Zero Trust Security Architecture
🛡️ Zero Trust Controls:
- Verify Explicitly: Every API call validates signature and expiration, never network origin.
- Least Privilege: Code checks
permissions.includes('delete:users')rather than broaduser.role === 'Admin'checks. - Assume Breach: Untrusted client headers (
x-user-id,x-user-roles) are stripped at the edge before proxying to internal services.
6. Azure APIM Gateway Policy
graph LR
subgraph Clients["Clients"]
UserBrowser["🌐 Browser (React SPA)"]
BackendDaemon["🤖 Backend Service"]
end
subgraph APIM["🛡️ Azure API Management Gateway"]
Strip["1. Strip untrusted client headers
(x-user-id, x-user-roles)"]
Validate["2. Validate JWT Signature & Expiration
(against Entra ID / Okta JWKS)"]
Normalize["3. Normalize Claims
(roles / groups -> x-user-roles)"]
end
subgraph Backend["⚡ Express API Backend (localhost:4000)"]
AuthMiddleware["requireAuth Middleware
(reads trusted x-user-* headers)"]
PermMiddleware["requirePermission Middleware
(checks fine-grained Least Privilege)"]
Endpoints["Target Route Handler
(e.g. DELETE /api/protected/users/:id)"]
end
UserBrowser -->|"Bearer Token (Entra/Okta)"| Strip
BackendDaemon -->|"Bearer Token (M2M)"| Strip
Strip --> Validate
Validate --> Normalize
Normalize -->|"Injected x-user-id, x-user-roles"| AuthMiddleware
AuthMiddleware --> PermMiddleware
PermMiddleware --> Endpoints
Inbound XML policy snippet for dual-IdP JWT validation and header injection:
<policies>
<inbound>
<base />
<set-header name="x-user-id" exists-action="delete" />
<set-header name="x-user-roles" exists-action="delete" />
<validate-jwt token-value="@(context.Request.Headers.GetValueOrDefault("Authorization","").Replace("Bearer ",""))"
failed-validation-httpcode="401">
<openid-config url="https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration" />
</validate-jwt>
<set-header name="x-user-id" exists-action="override">
<value>@(context.Request.Jwt.Claims.GetValueOrDefault("sub", "anonymous"))</value>
</set-header>
</inbound>
</policies>