Skip to content

Authentication and Security

harshcode1 edited this page Jun 20, 2026 · 1 revision

Authentication & Security

BetterMind uses a custom JWT auth layer rather than a turnkey framework, giving full control over the two-factor challenge and role gating.

Session model

  • On successful login the server signs a JWT and sets it as an httpOnly cookie named token (not readable by JavaScript → mitigates XSS token theft).
  • The client never stores the token; AuthContext learns the current user by calling GET /api/auth/check on mount.
  • Every protected Route Handler calls verifyAuth(token) from lib/authServer.js.

verifyAuth() with caching

verifyAuth(token):
  if no token            → { authenticated: false }
  if token in cache (TTL 5 min) → return cached result
  jwt.verify(token, JWT_SECRET)
  confirm user still exists in MongoDB
  (if doctor) attach doctorId
  cache + return { authenticated: true, user }

The 5-minute in-memory cache avoids a DB round-trip on every API call while still expiring quickly enough to reflect account changes.

Login flow (with optional 2FA)

POST /api/auth/login
        │
        ├─ password invalid ───────────────► 401
        │
        ├─ 2FA disabled ───► issue JWT cookie ───► { user }
        │
        └─ 2FA enabled ───► { requires2FA: true, userId }   ◄── no JWT yet!
                                   │
                                   ▼
                         /login/2fa challenge page
                                   │
                         POST /api/auth/2fa/verify { userId, code }
                                   │
                         code valid ───► issue JWT cookie ───► authenticated

The crucial property: the JWT is only issued after the TOTP code is verified, so a stolen password alone cannot create a session.

Two-Factor Authentication (TOTP)

Implemented with otplib v13 (which requires explicit crypto plugins — the Noble crypto + Scure base32 plugins are wired up in lib/twoFactorAuth.js).

Step Endpoint Result
Setup GET /api/auth/2fa/setup Generates a secret + otpauth:// URI; the client renders it as a QR via qrcode
Enable POST /api/auth/2fa/setup Verifies the first 6-digit code, stores the secret, returns one-time recovery codes
Challenge POST /api/auth/2fa/verify Used at login to validate a code (or recovery code) before issuing the JWT
Disable POST /api/auth/2fa/disable Requires a valid current code

The /settings/security page provides the full enable → QR scan → confirm → save-recovery-codes flow, and a confirm-to-disable flow.

Encryption

Sensitive data is encrypted at rest using AES (lib/encryption.js) with a key from ENCRYPTION_KEY. This keeps sensitive fields unreadable even with direct database access.

Rate limiting

A reusable limiter factory in lib/rateLimit.js throttles abuse-prone endpoints:

Endpoint Limit
Login throttled
Register 5 / 15 min
Chat 30 / min
Assessment 20 / hr

Exceeding a limit returns HTTP 429.

Role-Based Access Control (RBAC)

Three roles — patient, doctor, admin — enforced on both layers:

  • Server: handlers check user.role after verifyAuth.
  • Client: AuthContext exposes isDoctor(), isVerifiedDoctor(), isAdmin(), isPatient(), and pages redirect based on role.

Doctors additionally cannot accept appointments until an admin verifies their credentials (verified: true); rejected doctors see their rejectionReason.

Threats considered

Threat Mitigation
XSS token theft httpOnly cookie; token never in JS-readable storage
Password-only compromise TOTP 2FA gates JWT issuance
Brute force Rate limiting + bcrypt
Privilege escalation Server-side role checks on every protected route
Data-at-rest exposure AES encryption of sensitive fields
Unverified practitioners Admin verification workflow

Related

Clone this wiki locally