Skip to main content

General Notes

  • The auth middleware runs as a Fastify preParsing hook on every request
  • Tokens are nanoid(72) session tokens generated by the dtcea microservice — not JWTs
  • Session and user lookups happen on a separate auth DB connection (dtcea database)
  • Cerbos enforces RBAC after the user is identified
  • All auth failures return a generic "You are not authorized" message — no information leakage

Flow

Mermaid editor

Exclusion System

Routes opt out of auth steps via config.authentication.excludeFrom in the route definition.

validationAndVerification

Skips all authentication and authorization. The hook returns immediately without checking headers, sessions, or Cerbos.

Used for: health/info routes (Service Ping, Service Info, Mongo Ping, Route Info)

export const exampleConfig = {
routeId: "servicePing",
authentication: { excludeFrom: "validationAndVerification" },
authorization: { action: "read", resource: "general" },
};

verification

Validates the Authorization header format (must match ^Bearer\s.+$) but skips session lookup, user lookup, and Cerbos authorization. The hook returns after format validation succeeds.

Used for: logout-style routes where the token needs to be syntactically valid but may already be expired or invalidated.

export const exampleConfig = {
routeId: "logout",
authentication: { excludeFrom: "verification" },
authorization: { action: "create", resource: "auth" },
};

Default (no excludeFrom)

Full auth pipeline: token format validation, session lookup, user lookup, request.user population, and Cerbos RBAC check.

export const exampleConfig = {
routeId: "listCanvases",
authentication: {},
authorization: { action: "read", resource: "canvas" },
};

Multi-DB Connection

The application connects to two databases on the same MongoDB cluster:

ConnectionMethodDatabaseCollectionsPool Size
Primary DBmongoose.connect()default (from connection string)canvases, widgets, conversationsMONGO_MAX_POOL_SIZE (default 30)
Auth DBmongoose.createConnection()AUTH_DATABASE_NAME (dtcea)session, userAUTH_DB_MAX_POOL_SIZE (default 5)

Initialization Order

  1. dbService() connects both databases at startup
  2. initSessionModel() and initUserModel() register Mongoose models on the auth connection
  3. Models are accessed at request time via getSessionModel() and getUserModel()

Why a Separate Connection?

  • The dtcea microservice owns the auth database (users, sessions) — this service reads from it
  • Auth DB is read-only by convention — no writes from this service
  • Separate pool size keeps auth lookups from competing with primary DB operations
  • dbName option on createConnection() targets a different database on the same cluster

Cerbos Integration

After the user is identified, the hook calls Cerbos checkResource to enforce RBAC.

Principal Shape

{
id: user.userId, // e.g. "USR-AE-001"
roles: [user.userType], // e.g. ["AE"], ["CIO"], ["SUPER_ADMIN"]
attr: { // full user object (minus excluded fields)
userId: "USR-AE-001",
userType: "AE",
name: "Test User",
loginUserName: "testuser",
location: { sectionCode: "S01", divisionCode: "D01" },
createdAt: "2026-01-15T...",
updatedAt: "2026-03-20T...",
},
}

The attr field passes all user fields (including role-specific location fields) so Cerbos policies can write location-scoped rules.

Resource Shape

{
kind: authorizationConfig.resource, // e.g. "canvas", "widget", "conversation"
id: authorizationConfig.id || "idNotApplicable",
}
  • kind comes from config.authorization.resource in the route definition
  • id defaults to "idNotApplicable" when no specific resource ID is relevant (e.g., list/create routes)
  • id can be a function (request) => string for routes that operate on a specific resource

Actions

actions: [authorizationConfig.action]  // e.g. ["read"], ["create"], ["update"], ["delete"]

Single action per request, sourced from config.authorization.action in the route definition.

request.user Shape

After successful authentication and authorization, request.user is populated with the user document from the auth DB. Controllers can rely on this object for user-scoped queries.

request.user = {
userId: "USR-AE-001", // unique user identifier
userType: "AE", // role — "AE", "CIO", "SUPER_ADMIN"
name: "Test User", // display name
loginUserName: "testuser", // login identifier
location: { // role-specific location fields (varies by userType)
sectionCode: "S01",
divisionCode: "D01",
// additional fields depending on role (circleCode, zoneCode, etc.)
},
createdAt: "2026-01-15T...", // timestamps from dtcea
updatedAt: "2026-03-20T...",
};

Excluded Fields

The following fields are explicitly excluded via MongoDB projection and will never appear on request.user:

FieldReason
_idInternal MongoDB identifier — not exposed
loginPasswordSecurity — excluded via { loginPassword: 0 } projection
schemaVersionInternal versioning field from dtcea

Usage in Controllers

// Scope queries to the authenticated user
const canvases = await canvasModel
.find({ userId: request.user.userId })
.lean();

Security

Password Exclusion

loginPassword is excluded via MongoDB projection { loginPassword: 0 } on every user query. The user model has strict: false (to access discriminator location fields), so Mongoose does not strip unknown fields automatically — the projection is the sole security boundary.

Session Token Exclusion

sessionToken is excluded via MongoDB projection { sessionToken: 0 } on session queries. The token is only used for lookup — it is never stored on request or returned in responses.

Generic Error Messages

All authentication and authorization failures throw UnauthorizedError with the same message:

{ userMessage: "You are not authorized", sourceMessage: "Unauthorized" }

This applies to: missing header, invalid format, session not found, user not found, and Cerbos denial. No information is leaked about which step failed.

AJV Validation Errors

The AJV schema for the Authorization header defines errorMessage strings, but these are never exposed to users. If validation fails, the hook throws UnauthorizedError with the generic message — AJV error details are discarded.

Auth DB Read-Only Convention

The auth DB connection is read-only by convention. No model in this service performs writes to the auth database — session creation, user registration, and password management are handled by the dtcea microservice.