|
| 1 | +# Security Documentation - Staff Authentication & RBAC |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document describes the security measures implemented for staff authentication and role-based access control (RBAC) in the Niffy Insure backend. |
| 6 | + |
| 7 | +## Authentication Flow |
| 8 | + |
| 9 | +### Login Process |
| 10 | + |
| 11 | +Staff authenticate using email and password via `POST /api/auth/login`: |
| 12 | + |
| 13 | +``` |
| 14 | +POST /api/auth/login |
| 15 | +Content-Type: application/json |
| 16 | +
|
| 17 | +{ |
| 18 | + "email": "admin@example.com", |
| 19 | + "password": "securepassword" |
| 20 | +} |
| 21 | +``` |
| 22 | + |
| 23 | +Response: |
| 24 | +```json |
| 25 | +{ |
| 26 | + "accessToken": "eyJhbGciOiJIUzI1...", |
| 27 | + "refreshToken": "eyJhbGciOiJIUzI1...", |
| 28 | + "user": { |
| 29 | + "id": "uuid", |
| 30 | + "email": "admin@example.com", |
| 31 | + "role": "admin" |
| 32 | + } |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +### Token Usage |
| 37 | + |
| 38 | +Include the access token in the `Authorization` header: |
| 39 | +``` |
| 40 | +Authorization: Bearer <access_token> |
| 41 | +``` |
| 42 | + |
| 43 | +### Token Refresh |
| 44 | + |
| 45 | +Use the refresh token to obtain a new access token via `POST /api/auth/refresh`: |
| 46 | + |
| 47 | +``` |
| 48 | +POST /api/auth/refresh |
| 49 | +Content-Type: application/json |
| 50 | +
|
| 51 | +{ |
| 52 | + "refreshToken": "eyJhbGciOiJIUzI1..." |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +## Password Security |
| 57 | + |
| 58 | +- Passwords are hashed using **bcrypt** with salt rounds configured in `config.security.bcryptRounds` (default: 12) |
| 59 | +- Raw passwords are never stored or logged |
| 60 | +- Password verification failures are logged without exposing credentials |
| 61 | + |
| 62 | +## Role-Based Access Control (RBAC) |
| 63 | + |
| 64 | +### Roles |
| 65 | + |
| 66 | +| Role | Description | Permissions | |
| 67 | +|------|-------------|-------------| |
| 68 | +| `admin` | Full system administrator | All permissions | |
| 69 | +| `support_readonly` | Support staff with read-only access | `read:claims`, `read:policies`, `read:users`, `read:audit` | |
| 70 | + |
| 71 | +### Permission Matrix |
| 72 | + |
| 73 | +```typescript |
| 74 | +const ROLE_PERMISSIONS = { |
| 75 | + admin: [ |
| 76 | + 'read:claims', 'write:claims', |
| 77 | + 'read:policies', 'write:policies', |
| 78 | + 'read:users', 'write:users', |
| 79 | + 'read:audit', 'write:audit', |
| 80 | + 'read:settings', 'write:settings', |
| 81 | + 'read:reports' |
| 82 | + ], |
| 83 | + support_readonly: [ |
| 84 | + 'read:claims', 'read:policies', 'read:users', 'read:audit' |
| 85 | + ] |
| 86 | +}; |
| 87 | +``` |
| 88 | + |
| 89 | +### Support Staff Limitations |
| 90 | + |
| 91 | +Support staff (`support_readonly` role): |
| 92 | +- Can view claims, policies, users, and audit logs |
| 93 | +- Cannot create, update, or delete any resources |
| 94 | +- Cannot access system settings or administrative functions |
| 95 | +- Cannot generate reports |
| 96 | + |
| 97 | +## Token Configuration |
| 98 | + |
| 99 | +### Access Token |
| 100 | + |
| 101 | +- **Expiry**: 15 minutes (configurable via `config.jwt.accessTokenExpiry`) |
| 102 | +- **Algorithm**: HS256 |
| 103 | +- **Payload**: `{ id, email, role, iat, exp }` |
| 104 | + |
| 105 | +### Refresh Token |
| 106 | + |
| 107 | +- **Expiry**: 7 days (configurable via `config.jwt.refreshTokenExpiry`) |
| 108 | +- **Algorithm**: HS256 |
| 109 | +- **Payload**: `{ id, email, role, iat, exp, type: 'refresh' }` |
| 110 | + |
| 111 | +## Security Headers |
| 112 | + |
| 113 | +The following security headers are enforced via Helmet: |
| 114 | + |
| 115 | +- `X-Content-Type-Options: nosniff` |
| 116 | +- `X-Frame-Options: DENY` |
| 117 | +- `X-XSS-Protection: 1; mode=block` |
| 118 | +- `Strict-Transport-Security: max-age=31536000; includeSubDomains` |
| 119 | +- `Content-Security-Policy` (customizable) |
| 120 | + |
| 121 | +## CORS Configuration |
| 122 | + |
| 123 | +CORS is configured to allow specific origins (configurable via `config.security.corsOrigins`). In production, restrict to known frontend domains. |
| 124 | + |
| 125 | +## Rate Limiting |
| 126 | + |
| 127 | +- **Login attempts**: 5 requests per 15 minutes per IP |
| 128 | +- **General API**: 100 requests per 15 minutes per IP |
| 129 | +- Rate limit headers are included in responses (`X-RateLimit-Limit`, `X-RateLimit-Remaining`) |
| 130 | + |
| 131 | +## Logging Security |
| 132 | + |
| 133 | +Authentication failures are logged without exposing sensitive information: |
| 134 | +- ✅ Logged: email (partial), IP, timestamp, failure reason |
| 135 | +- ❌ Not logged: passwords, tokens, full user details |
| 136 | + |
| 137 | +Example log output: |
| 138 | +``` |
| 139 | +[AUTH] Login failed for user: admin@***.com from IP: 192.168.1.1 - Invalid credentials |
| 140 | +[AUTH] Token verification failed: TokenExpiredError |
| 141 | +[AUTH] Role check failed: user support@example.com with role support_readonly attempted to access POST /admin/users requiring admin |
| 142 | +``` |
| 143 | + |
| 144 | +## Threat Model |
| 145 | + |
| 146 | +### XSS (Cross-Site Scripting) |
| 147 | + |
| 148 | +**Risk**: Attackers could steal JWT tokens stored in browser localStorage. |
| 149 | + |
| 150 | +**Mitigation**: |
| 151 | +- Consider using httpOnly cookies for token storage (requires CSRF protection) |
| 152 | +- Implement token rotation on suspicious activity |
| 153 | +- Set short expiry for access tokens (15 min) |
| 154 | +- Frontend should implement Content Security Policy |
| 155 | + |
| 156 | +**Recommendation**: For production, implement httpOnly secure cookies with CSRF tokens. |
| 157 | + |
| 158 | +### CSRF (Cross-Site Request Forgery) |
| 159 | + |
| 160 | +**Risk**: Attackers could make authenticated requests on behalf of users. |
| 161 | + |
| 162 | +**Mitigation**: |
| 163 | +- Use `SameSite` cookie attribute (strict/lax) |
| 164 | +- Implement CSRF token validation for state-changing operations |
| 165 | +- Use double-submit cookie pattern |
| 166 | + |
| 167 | +**Production Setup**: When using cookies, implement CSRF protection. |
| 168 | + |
| 169 | +### Token Theft |
| 170 | + |
| 171 | +**Risk**: Tokens could be intercepted in transit or stolen from storage. |
| 172 | + |
| 173 | +**Mitigation**: |
| 174 | +- Enforce HTTPS in production (see below) |
| 175 | +- Short access token expiry (15 min) |
| 176 | +- Refresh token rotation on use |
| 177 | +- Implement token revocation mechanism |
| 178 | + |
| 179 | +### Brute Force Attacks |
| 180 | + |
| 181 | +**Risk**: Attackers could guess passwords via brute force. |
| 182 | + |
| 183 | +**Mitigation**: |
| 184 | +- Rate limiting on login endpoint (5 attempts per 15 min) |
| 185 | +- bcrypt with high salt rounds (12) |
| 186 | +- Account lockout after failed attempts (future enhancement) |
| 187 | + |
| 188 | +## Production Requirements |
| 189 | + |
| 190 | +### HTTPS Enforcement |
| 191 | + |
| 192 | +⚠️ **Critical**: The application MUST be deployed behind HTTPS in production. |
| 193 | + |
| 194 | +```bash |
| 195 | +# Environment variables for production |
| 196 | +NODE_ENV=production |
| 197 | +FORCE_SSL=true |
| 198 | +``` |
| 199 | + |
| 200 | +Configuration for reverse proxy (nginx example): |
| 201 | +```nginx |
| 202 | +server { |
| 203 | + listen 443 ssl http2; |
| 204 | + server_name api.niffyinsure.com; |
| 205 | + |
| 206 | + ssl_certificate /path/to/cert.pem; |
| 207 | + ssl_certificate_key /path/to/key.pem; |
| 208 | + ssl_protocols TLSv1.2 TLSv1.3; |
| 209 | + |
| 210 | + location / { |
| 211 | + proxy_pass http://localhost:3000; |
| 212 | + proxy_set_header X-Forwarded-Proto $scheme; |
| 213 | + } |
| 214 | +} |
| 215 | +``` |
| 216 | + |
| 217 | +### Secure Cookie Configuration |
| 218 | + |
| 219 | +When using cookies for token storage, configure: |
| 220 | + |
| 221 | +```typescript |
| 222 | +// Example cookie options for production |
| 223 | +const cookieOptions = { |
| 224 | + httpOnly: true, |
| 225 | + secure: true, // HTTPS only |
| 226 | + sameSite: 'strict', // CSRF protection |
| 227 | + maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days |
| 228 | + path: '/' |
| 229 | +}; |
| 230 | +``` |
| 231 | + |
| 232 | +### Environment Variables |
| 233 | + |
| 234 | +Required production configuration: |
| 235 | + |
| 236 | +```bash |
| 237 | +# JWT Configuration |
| 238 | +JWT_SECRET=<minimum-256-bit-secret> |
| 239 | +JWT_REFRESH_SECRET=<minimum-256-bit-secret> |
| 240 | + |
| 241 | +# Security |
| 242 | +BCRYPT_ROUNDS=12 |
| 243 | +NODE_ENV=production |
| 244 | + |
| 245 | +# HTTPS (via reverse proxy) |
| 246 | +# FORCE_SSL=true |
| 247 | +``` |
| 248 | + |
| 249 | +## Key Rotation |
| 250 | + |
| 251 | +### JWT Signing Keys |
| 252 | + |
| 253 | +**Recommendation**: Rotate JWT signing keys periodically. |
| 254 | + |
| 255 | +Implementation approach: |
| 256 | +1. Maintain multiple key versions in configuration |
| 257 | +2. Use key ID (`kid`) in JWT header to identify which key was used |
| 258 | +3. Accept tokens signed by current and previous keys during transition |
| 259 | +4. Schedule key rotation every 90 days |
| 260 | + |
| 261 | +```typescript |
| 262 | +// Key rotation configuration |
| 263 | +const jwtOptions = { |
| 264 | + issuer: 'niffyinsure', |
| 265 | + audience: 'niffyinsure-api', |
| 266 | + algorithm: 'HS256', |
| 267 | + keyid: 'key-v1' // Rotate this value when changing keys |
| 268 | +}; |
| 269 | +``` |
| 270 | + |
| 271 | +### Refresh Token Rotation |
| 272 | + |
| 273 | +- Each refresh creates a new refresh token |
| 274 | +- Store refresh tokens in database for revocation capability |
| 275 | +- Invalidate refresh tokens on password change or logout |
| 276 | +- Implement token blacklist for immediate revocation |
| 277 | + |
| 278 | +## MFA Roadmap |
| 279 | + |
| 280 | +Multi-factor authentication is planned for future implementation: |
| 281 | + |
| 282 | +### Phase 1 (Post-Launch) |
| 283 | +- TOTP-based authenticator apps (Google Authenticator, Authy) |
| 284 | +- Recovery codes |
| 285 | + |
| 286 | +### Phase 2 |
| 287 | +- Email-based verification for sensitive actions |
| 288 | +- Session management (view/revoke active sessions) |
| 289 | + |
| 290 | +### Phase 3 |
| 291 | +- SMS/Email OTP for login confirmation |
| 292 | +- Hardware security keys (WebAuthn/FIDO2) |
| 293 | + |
| 294 | +### Implementation Notes |
| 295 | + |
| 296 | +```typescript |
| 297 | +// Future MFA types |
| 298 | +interface MFAMethods { |
| 299 | + totp: boolean; |
| 300 | + email: boolean; |
| 301 | + sms: boolean; |
| 302 | + webauthn: boolean; |
| 303 | +} |
| 304 | + |
| 305 | +// Login flow with MFA |
| 306 | +interface LoginResponse { |
| 307 | + accessToken: string; |
| 308 | + mfaRequired: boolean; |
| 309 | + mfaMethod: 'totp' | 'email' | 'sms'; |
| 310 | +} |
| 311 | + |
| 312 | +// Second factor verification endpoint |
| 313 | +POST /api/auth/verify-mfa |
| 314 | +{ |
| 315 | + "tempToken": "...", |
| 316 | + "code": "123456" |
| 317 | +} |
| 318 | +``` |
| 319 | + |
| 320 | +## Security Testing |
| 321 | + |
| 322 | +### Test Matrix (Implemented) |
| 323 | + |
| 324 | +| Scenario | Expected Status | |
| 325 | +|----------|-----------------| |
| 326 | +| No auth header on protected route | 401 Unauthorized | |
| 327 | +| Invalid token format | 401 Unauthorized | |
| 328 | +| Expired token | 401 Unauthorized | |
| 329 | +| Valid token, insufficient role | 403 Forbidden | |
| 330 | +| Valid token, correct role | 200 OK | |
| 331 | + |
| 332 | +Run tests: |
| 333 | +```bash |
| 334 | +npm test |
| 335 | +``` |
| 336 | + |
| 337 | +### Additional Security Tests (Recommended) |
| 338 | + |
| 339 | +- Rate limiting effectiveness |
| 340 | +- SQL injection prevention |
| 341 | +- XSS prevention in error messages |
| 342 | +- Password strength requirements |
| 343 | +- Session timeout verification |
| 344 | + |
| 345 | +## API Endpoints Summary |
| 346 | + |
| 347 | +| Endpoint | Method | Auth Required | Role Required | |
| 348 | +|----------|--------|---------------|---------------| |
| 349 | +| `/api/auth/login` | POST | No | - | |
| 350 | +| `/api/auth/refresh` | POST | No | - | |
| 351 | +| `/api/auth/me` | GET | Yes | Any | |
| 352 | +| `/api/auth/logout` | POST | Yes | Any | |
| 353 | +| `/api/admin/dashboard` | GET | Yes | admin | |
| 354 | +| `/api/admin/users` | GET/POST | Yes | admin | |
| 355 | +| `/api/admin/users/:id` | GET/PUT/DELETE | Yes | admin | |
| 356 | +| `/api/admin/policies` | GET/POST | Yes | admin | |
| 357 | +| `/api/admin/claims` | GET/POST | Yes | admin | |
| 358 | +| `/api/admin/audit` | GET | Yes | admin/support_readonly | |
| 359 | +| `/api/admin/settings` | GET/PUT | Yes | admin | |
| 360 | +| `/api/admin/reports` | GET | Yes | admin | |
| 361 | + |
| 362 | +## References |
| 363 | + |
| 364 | +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) |
| 365 | +- [JWT Best Practices](https://datatracker.ietf.org/doc/html/rfc8725) |
| 366 | +- [Helmet.js](https://helmetjs.github.io/) |
| 367 | +- [Express Rate Limit](https://github.com/express-rate-limit/express-rate-limit) |
| 368 | +- [BCrypt](https://github.com/kelektiv/node.bcrypt.js) |
0 commit comments