Authentication System
JWT-based authentication and authorization system - login flow, token management, role-based access control
Authentication System
Introduction
JWT-based authentication with dual paths: primary auth in Apollo context factory and fallback in authChecker. Three user roles (USER, ADMIN, SUPER_ADMIN) with role-based access control. Integrates with GraphQL API and Socket.io.
Authentication Architecture
Primary Path: Apollo context factory authenticates every GraphQL request, populates context.user.
Fallback Path: AuthChecker re-authenticates when resolvers have @Authorized decorator.
Both paths use shared verifyRequestAuth function for consistent JWT verification. Authentication metadata tracks which path was used.
User Model and Roles
model User {
id String @id @default(uuid())
email String @unique
password String // bcrypt hashed
firstName String
lastName String
role UserRole @default(USER)
deleted Boolean @default(false)
token String? // Legacy, not actively used
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum UserRole {
USER // Manage own VMs and resources
ADMIN // Manage all users and resources
SUPER_ADMIN // Can create/modify other SUPER_ADMIN users
}
SafeUser type excludes password and token fields: export type SafeUser = Omit<User, 'password' | 'token'>
Login Flow
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user { id email firstName lastName role }
}
}
Process: Find user by email → verify password with bcrypt → generate JWT → return user and token. Returns generic AuthenticationError('Invalid credentials') on failure to prevent user enumeration.
Token generation: Payload { userId, userRole }, signed with TOKENKEY env var, no expiration (long-lived), HS256 algorithm.
⚠️ Security: Login resolver uses insecure fallback 'secret' if TOKENKEY missing. Must set TOKENKEY in all environments and remove fallback.
Client storage: Token stored in browser (localStorage/sessionStorage), included in Authorization: Bearer <token> header for all requests.
Token Verification
Token extracted from Authorization header (supports Bearer <token> format and direct token). JWT secret from TOKENKEY env var (required in production, fallback in dev if ALLOW_INSECURE_JWT_FALLBACK=1).
Verification: Extract token → get JWT secret → verify signature → validate payload (userId, userRole) → check expiration → fetch user from database → check user exists and not deleted → verify role matches → return SafeUser.
Payload validation ensures userId and userRole fields exist. User validation checks user exists, not deleted, role matches token.
Apollo Context
Context factory runs for every GraphQL request, calls verifyRequestAuth, returns context with user (SafeUser | null), auth metadata, userHelpers, prisma, req, res.
Authentication metadata tracks: method (context/fallback/none), status (authenticated/unauthenticated/token_expired/token_invalid/user_not_found/user_deleted/role_mismatch), warnings, timestamp, tokenExpiration, tokenRole.
User validation helpers: isAuthenticated(), hasRole(role), isAdmin(), getDisplayName(), isAuthenticationFresh().
AuthChecker Fallback
Triggers when resolver has @Authorized decorator and context.user is null. Re-authenticates via verifyRequestAuth, updates context if successful, checks role-based access.
Access checking: No roles required → allow. SETUP_MODE role → check context.setupMode. No user → deny. Otherwise check if user has required role (USER = any authenticated user, ADMIN = ADMIN or SUPER_ADMIN).
@Authorized Decorator
@Query(() => User)
@Authorized('USER')
async currentUser(@Ctx() ctx: InfinibayContext) { return ctx.user!; }
@Query(() => [User])
@Authorized('ADMIN')
async users(@Ctx() ctx: InfinibayContext) { return ctx.prisma.user.findMany(); }
Supports multiple roles (OR logic): @Authorized('USER', 'ADMIN'). Returns UNAUTHORIZED error if access denied.
Role Capabilities
USER: Manage own VMs, view own department, update own profile. Cannot create users or access other users' resources.
ADMIN: All USER capabilities + create/manage users (except SUPER_ADMIN), manage all VMs/departments, system settings.
SUPER_ADMIN: All ADMIN capabilities + create/modify SUPER_ADMIN users, system-level configuration.
SUPER_ADMIN protections: Only SUPER_ADMIN can create other SUPER_ADMIN users, cannot demote SUPER_ADMIN users, role mismatch detection prevents privilege escalation.
Department-Scoped Authorization
ADMIN/SUPER_ADMIN: Access all departments. USER: Access only assigned departments. Utilities: getUserAccessibleDepartments, validateDepartmentAccess, getDepartmentScopedWhereClause.
Error Handling
Error flow: Token verification categorizes errors (logged server-side) → authChecker returns true/false → resolvers throw typed errors (AuthenticationError, UserInputError, ForbiddenError) → Apollo formatError maps to client codes (UNAUTHORIZED, FORBIDDEN, BAD_USER_INPUT).
Error categories (server-side only): token_expired, invalid_signature, invalid_payload_*, user_not_found, user_deleted, role_mismatch.
Password Management
bcrypt with 10 rounds, passwords validated for length/complexity, update requires current password verification.
Token Storage
Client stores token in localStorage/sessionStorage, includes in Authorization: Bearer <token> header. Secure transmission over HTTPS.
Socket.io Authentication
JWT authentication on WebSocket connection via socket.handshake.auth.token. Auto-join user-specific room user:{userId}.
Environment Variables
TOKENKEY(required): JWT signing secretDEBUG_AUTH(optional): Enable auth debugging (0/1)ALLOW_INSECURE_JWT_FALLBACK(optional): Allow fallback secret in dev (0/1)
Summary
JWT-based auth with dual paths (context + authChecker fallback), role-based access control (USER/ADMIN/SUPER_ADMIN), department-scoped authorization, comprehensive error handling. Key files: app/utils/jwtAuth.ts, app/utils/authChecker.ts, app/config/apollo.ts, app/index.ts.