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 on http://localhost:3000 for local OIDC lab authentication (Okta & Entra ID).
  • apps/api-server: Express 5 TypeScript REST API backend running on http://localhost:4000 enforcing 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/publicNoneAnonymous
GET/api/public/healthNoneAnonymous
POST/api/auth/mock-tokenNoneAnonymous
GET/api/protected/meAuthenticatedAll Roles
GET/api/protected/usersread:usersAdmin, Manager, Developer
POST/api/protected/userswrite:usersAdmin
DELETE/api/protected/users/:iddelete:usersAdmin
GET/api/protected/reportsread:reportsAdmin, Manager, Developer, Auditor
POST/api/protected/reportswrite:reportsAdmin, Manager
PUT/api/protected/settingswrite:settingsAdmin
GET/api/protected/auditread:auditAdmin, Auditor
DELETE/api/protected/auditdelete:auditAdmin
POST/api/protected/jobsexecute:jobsAdmin, 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 broad user.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>