General Notes
- The auth middleware runs as a Fastify
preParsinghook 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
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:
| Connection | Method | Database | Collections | Pool Size |
|---|---|---|---|---|
| Primary DB | mongoose.connect() | default (from connection string) | canvases, widgets, conversations | MONGO_MAX_POOL_SIZE (default 30) |
| Auth DB | mongoose.createConnection() | AUTH_DATABASE_NAME (dtcea) | session, user | AUTH_DB_MAX_POOL_SIZE (default 5) |
Initialization Order
dbService()connects both databases at startupinitSessionModel()andinitUserModel()register Mongoose models on the auth connection- Models are accessed at request time via
getSessionModel()andgetUserModel()
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
dbNameoption oncreateConnection()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",
}
kindcomes fromconfig.authorization.resourcein the route definitioniddefaults to"idNotApplicable"when no specific resource ID is relevant (e.g., list/create routes)idcan be a function(request) => stringfor 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:
| Field | Reason |
|---|---|
_id | Internal MongoDB identifier — not exposed |
loginPassword | Security — excluded via { loginPassword: 0 } projection |
schemaVersion | Internal 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.