# Authentication and Security This document covers the complete authentication, authorization, and security system for the Pediatric AI Scribe application. --- ## Authentication Methods ### Local Authentication - Passwords are hashed using **bcryptjs** with 12 salt rounds. - On successful login, a **JSON Web Token (JWT)** is issued with a 7-day expiry. - The token is stored in an **httpOnly cookie** named `ped_auth`. - In production: `secure: true`, `sameSite: lax`. - In development: `secure: false`, `sameSite: lax`. ### Auth Middleware The authentication middleware checks credentials in the following order: 1. Looks for a `Bearer` token in the `Authorization` header. 2. If the token is empty or missing (including the case where the header is literally `"Bearer "` with no token), falls back to reading the `ped_auth` cookie. This two-step approach was specifically fixed to handle empty Bearer strings gracefully, preventing false authentication failures from clients that send the header with no value. ### TOTP Two-Factor Authentication (2FA) - Implemented using the **speakeasy** library. - Setup flow: server generates a TOTP secret, encodes it as a QR code, and the user scans it with an authenticator app. - Verification: 6-digit code, with `window=1` (accepts codes from the previous and next 30-second interval in addition to the current one). ### OIDC / SSO (Single Sign-On) - Implements the **Authorization Code + PKCE** flow using the **openid-client** library. - State parameters are stored **in-memory** with a 5-minute TTL to prevent replay attacks. - On first login via OIDC, a local user account is **auto-created** using claims from the identity provider. - Supported providers: - Azure AD - Okta - Keycloak - PocketID - Google ### Email Verification - A 32-byte random hex token is generated and sent to the user's email address. - The token expires after **24 hours**. ### Password Reset - A 32-byte random hex token is generated and sent to the user's email address. - The token expires after **1 hour**. --- ## Cloudflare Turnstile (CAPTCHA) Turnstile is applied to the following routes: - User registration - User login - Password reset request ### Frontend - The `cf-turnstile` widget is rendered with the configured site key. - The form validates that the Turnstile challenge was completed before allowing submission. - On failure, the widget resets so the user can retry. ### Backend - The server sends a `POST` request to `https://challenges.cloudflare.com/turnstile/v0/siteverify` with the secret key and the client-provided token. - Turnstile is **only enforced when `TURNSTILE_SECRET_KEY` is set** in the environment. If the variable is absent, the check is skipped entirely. This allows development and self-hosted environments to run without Cloudflare integration. --- ## Rate Limiting Rate limiting is implemented using **express-rate-limit** with the following windows: | Endpoint | Limit | Window | |----------------------------------|---------------|----------| | `/api/*` (general) | 60 requests | 1 minute | | `/api/auth/login` | 10 requests | 15 minutes | | `/api/auth/register` | 5 requests | 1 hour | | `/api/auth/forgot-password` | 5 requests | 1 hour | | `/api/auth/resend-verification` | 3 requests | 15 minutes | --- ## Content Security Policy (Helmet) The application uses **Helmet** to set HTTP security headers. The Content Security Policy directives are configured as follows: | Directive | Values | |------------------|------------------------------------------------------------------------| | `script-src` | `'self'`, `'wasm-unsafe-eval'`, `'unsafe-eval'`, `cdn.jsdelivr.net`, `challenges.cloudflare.com` | | `script-src-attr`| `'none'` (blocks inline event handlers like `onclick`) | | `style-src` | `'self'`, `'unsafe-inline'`, `fonts.googleapis.com`, `cdnjs.cloudflare.com` | | `frame-src` | `'self'`, `challenges.cloudflare.com` | | `connect-src` | `'self'` + CDN domains + HuggingFace + Cloudflare | | `object-src` | `'none'` | --- ## CORS - **Production** (when `APP_URL` is set): restricts the allowed origin to the value of `APP_URL`. - **Development** (when `APP_URL` is not set): allows all origins. - Requests with **no origin** (such as those from mobile apps or `curl`) are always permitted. - `credentials: true` is set to allow cookies to be sent cross-origin. --- ## Roles and Authorization The application defines three user roles: | Role | Access Level | |-------------|-------------------------------------------------------| | `admin` | Full access to all features, including the Admin Panel. The **first registered user** is automatically promoted to admin. | | `moderator` | Standard user access plus Learning Hub CMS management. | | `user` | Standard access to patient encounters and AI features. | --- ## Audit Logging All authentication-related events are recorded in the `audit_log` database table. ### Fields | Column | Description | |--------------|--------------------------------------------------| | `user_id` | The ID of the user involved (null for failed attempts by unknown users) | | `action` | The type of event (see below) | | `ip_address` | The client IP address | | `details` | A JSON object with additional context | ### Tracked Actions - `register` -- new account created - `login` -- successful login - `login_failed` -- incorrect credentials - `login_blocked` -- blocked by rate limiter or other policy - `email_verified` -- user confirmed their email address - Additional actions for password resets, 2FA changes, and OIDC logins