Skip to content

Authorization

Statement

Every authenticated request to ClaimGuard passes through a two-axis authorization check: a role test (the caller's app_role allows the action) and an org-scope test (the row being read or written belongs to the caller's org_ref, or the caller is a super-admin). Both checks are enforced at the application layer in middleware and in route handlers; there is no client-side role gate that, if bypassed, exposes data.

The model is small on purpose — four roles and one tenant attribute — so the surface area auditors review is small, and so a reviewer reading a route handler can verify the authorization story by inspection.

Implementation

Role model

Four built-in roles are defined and enforced in server/src/middleware/auth.js:

Role Scope Notes
SUPER_ADMIN Cross-org Bootstrap administrators. Can create other super-admins. Can administer any org's resources.
ADMIN Single org Org-scoped administration: user management, configuration, claim review.
REVIEWER Single org Default analyst role. Reviews claims, writes notes.
EXEC_VIEWER Single org Read-only dashboard access.

Role grants happen via user creation (POST /api/users) and update (PUT /api/users/:id). Only super-admins can grant SUPER_ADMIN. Admins can grant non-super-admin roles within their own org.

Role enforcement

Three middlewares are exported from server/src/middleware/auth.js:

  • requireAuth — verifies a valid JWT is present, then re-fetches the user row from the DB and populates req.user with the current id, email, app_role, org_ref, display_name, and full_name. Returns 401 on missing/invalid token, expired token, or a user whose is_active is false. API keys are a separate authentication path handled by server/src/middleware/apiKeyAuth.js, not by requireAuth.
  • requireAdmin — calls requireAuth first, then checks req.user.app_role is one of ['ADMIN', 'SUPER_ADMIN']. Returns 403 otherwise.
  • requireSuperAdmin — calls requireAuth first, then checks req.user.app_role === 'SUPER_ADMIN'. Returns 403 otherwise.

A route's authorization is therefore visible at the route declaration:

// Reviewer-or-up:
router.get('/api/claims/:id', requireAuth, ...)

// Admin-or-up:
router.put('/api/users/:id', requireAdmin, ...)

// Super-admin only:
router.delete('/api/orgs/:id', requireSuperAdmin, ...)

Org-scope enforcement

Org scoping is enforced at the SQL layer of every route that touches tenant data. The pattern (visible in server/src/routes/_crud.js and in route-specific handlers) is:

  • Every tenant-bound row carries an org_ref foreign-key column.
  • Every list query filters on WHERE org_ref = $org_ref.
  • Every detail / update / delete query joins or filters on the same.
  • Super-admins bypass the filter for cross-org administration.

A representative example from server/src/routes/users.js lines 108-112:

// Non-super-admins can only view users in their organization
if (req.user.app_role !== 'SUPER_ADMIN' &&
    user.org_ref && user.org_ref !== req.user.org_ref) {
  return res.status(403).json({ error: 'Access denied' });
}

This pattern is the application's IDOR (Insecure Direct Object Reference) defense: even if a caller guesses or enumerates valid resource IDs from another org, the org-scope check returns 403.

API-key authorization

API keys carry their own authorization data:

  • org_ref — the key's tenant. Every query made under the key is scoped the same way as a human request from that org.
  • scopes — a per-key array consulted in server/src/middleware/apiKeyAuth.js. Routes that require API-key auth check the scope set before serving.
  • Issuer constraint — admins can only create keys for their own org; super-admins can create keys for any org.

See Authentication for the full API-key model.

What runs in the middleware chain

For a typical authenticated route, the request passes through:

  1. CORS / Helmet / standard middleware (server/src/index.js).
  2. IP-based rate limiter (per docs/security/README.mdserver/src/middleware/rateLimit.js).
  3. requireAuth (or requireAdmin / requireSuperAdmin) — identity + role.
  4. Per-user post-auth rate limiter for admin-class endpoints.
  5. Zod validator for request body / params (validate middleware).
  6. Route handler — runs the org-scoped query.

Cross-cutting invariants

docs/security/README.md lists the authorization-relevant invariants this codebase depends on:

  • The audit-log endpoint is org-scoped (super-admins see cross-org; others see only their own org).
  • The rate limiter is mounted after auth for admin endpoints, so it can scope by user, not just by IP.
  • Routes that operate on a resource by ID always verify ownership before action, never trusting that the listing routes already filtered.

A PR that weakens any of these invariants must either add an equivalent invariant or document the regression with a .snyk-style written reason — see Change management.

Status

implemented — verified 2026-04-30.

What's in place:

  • Four-role model with documented capability boundaries.
  • Three middleware wrappers (requireAuth, requireAdmin, requireSuperAdmin) used at every protected route.
  • Per-request DB re-fetch in requireAuthapp_role and org_ref are read from the live users row on every request, so role downgrades and org transfers take effect on the user's next call without a re-login.
  • Org-scoping at the SQL layer with super-admin override.
  • IDOR defense via consistent org-scope checks in detail / update / delete routes.
  • API-key scopes and per-key org binding.

Known gaps

  • No fine-grained permissions. Roles are coarse; a customer that wants "REVIEWER without claim-delete" or "REVIEWER with read-only exports" must wait for a more granular permission model.
  • No external attribute-based access control (ABAC). All authorization decisions are encoded in code, not in an external policy engine (e.g., OPA). Fine for current scale; revisit if a customer demands externalized policy.
  • No documented periodic access review. Quarterly review of human-user roles and API-key inventory is a roadmap item.

Roadmap

  • Quarterly access review of users + API keys, with sign-off recorded — before SOC 2 fieldwork.
  • Fine-grained permissions ("entitlements") if a customer requirement demands them.
  • Externalized policy engine (OPA / Cedar) — not before there's a customer need.