CalDAV: Support Basic auth; trim+validate UUID; deprecate query token via headers; ICS end time helper; docs+instructions updated
This commit is contained in:
251
docs/session-management.md
Normal file
251
docs/session-management.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Session Management & CSRF Protection
|
||||
|
||||
## Overview
|
||||
|
||||
This application uses **HttpOnly cookie-based session management** with CSRF protection to provide secure authentication while protecting against common web vulnerabilities like XSS and CSRF attacks.
|
||||
|
||||
### Security Benefits
|
||||
|
||||
- **XSS Protection**: SessionId stored in HttpOnly cookies is not accessible to malicious JavaScript
|
||||
- **CSRF Protection**: Double-submit cookie pattern prevents cross-site request forgery
|
||||
- **Session Rotation**: New sessions created after login and password changes prevent session fixation
|
||||
- **GDPR Compliance**: HttpOnly cookies provide better privacy protection than localStorage
|
||||
|
||||
## Architecture
|
||||
|
||||
### Session Storage
|
||||
Sessions are stored in an in-memory KV store with the following structure:
|
||||
```typescript
|
||||
type Session = {
|
||||
id: string;
|
||||
userId: string;
|
||||
expiresAt: string;
|
||||
createdAt: string;
|
||||
csrfToken?: string;
|
||||
}
|
||||
```
|
||||
|
||||
- **Expiration**: 24 hours
|
||||
- **Storage**: In-memory KV store (single-instance deployment)
|
||||
- **CSRF Token**: Cryptographically secure 64-character hex string
|
||||
|
||||
### Cookie Configuration
|
||||
|
||||
#### Session Cookie (`sessionId`)
|
||||
- **Type**: HttpOnly, Secure (production), SameSite=Lax
|
||||
- **Path**: `/`
|
||||
- **MaxAge**: 86400 seconds (24 hours)
|
||||
- **Purpose**: Authenticates user across requests
|
||||
|
||||
#### CSRF Cookie (`csrf-token`)
|
||||
- **Type**: Non-HttpOnly, Secure (production), SameSite=Lax
|
||||
- **Path**: `/`
|
||||
- **MaxAge**: 86400 seconds (24 hours)
|
||||
- **Purpose**: Provides CSRF token for JavaScript to include in requests
|
||||
|
||||
### CSRF Protection
|
||||
|
||||
The application uses the **double-submit cookie pattern**:
|
||||
|
||||
1. **Server-side**: CSRF token generated and stored in session
|
||||
2. **Client-side**: Same token stored in non-HttpOnly cookie
|
||||
3. **Validation**: Token from `X-CSRF-Token` header must match session token
|
||||
4. **Timing-safe comparison**: Prevents timing attacks
|
||||
|
||||
### Session Rotation
|
||||
|
||||
Sessions are automatically rotated (new session created, old invalidated) after:
|
||||
- Successful login
|
||||
- Password changes
|
||||
|
||||
This prevents session fixation attacks.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Server-side
|
||||
|
||||
#### Cookie Parsing Middleware (`src/server/routes/rpc.ts`)
|
||||
```typescript
|
||||
// Cookie parsing middleware - extracts sessionId from cookies
|
||||
rpcApp.use("/*", async (c, next) => {
|
||||
try {
|
||||
const sessionId = getCookie(c, SESSION_COOKIE_NAME);
|
||||
c.set('sessionId', sessionId || null);
|
||||
await next();
|
||||
} catch (error) {
|
||||
console.error("Cookie parsing error:", error);
|
||||
c.set('sessionId', null);
|
||||
await next();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Authentication Helper (`src/server/lib/auth.ts`)
|
||||
Key functions:
|
||||
- `generateCSRFToken()`: Creates cryptographically secure token
|
||||
- `getSessionFromCookies(c)`: Extracts and validates session from cookies
|
||||
- `validateCSRFToken(c, sessionId)`: Validates CSRF token from header
|
||||
- `assertOwner(c)`: Validates owner role with session and CSRF checks
|
||||
- `rotateSession(oldSessionId, userId)`: Creates new session, invalidates old
|
||||
|
||||
#### RPC Handler Updates
|
||||
All admin-only RPC handlers now:
|
||||
- Accept Hono `context` parameter
|
||||
- Use `assertOwner(context)` for authentication
|
||||
- Remove `sessionId` from input schemas
|
||||
- Automatically get session from cookies
|
||||
|
||||
### Client-side
|
||||
|
||||
#### RPC Client Configuration (`src/client/rpc-client.ts`)
|
||||
```typescript
|
||||
const link = new RPCLink({
|
||||
url: `${window.location.origin}/rpc`,
|
||||
headers: () => {
|
||||
const csrfToken = getCSRFToken();
|
||||
return csrfToken ? { 'X-CSRF-Token': csrfToken } : {};
|
||||
},
|
||||
fetch: (request, init) => {
|
||||
return fetch(request, {
|
||||
...init,
|
||||
credentials: 'include' // Include cookies with all requests
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### AuthProvider Updates (`src/client/components/auth-provider.tsx`)
|
||||
- Removed all localStorage usage
|
||||
- Sessions managed entirely server-side
|
||||
- No client-side sessionId storage
|
||||
|
||||
## Security Features
|
||||
|
||||
### XSS Protection
|
||||
- SessionId not accessible to JavaScript (HttpOnly cookie)
|
||||
- Malicious scripts cannot steal session tokens
|
||||
|
||||
### CSRF Protection
|
||||
- Token validation on all state-changing operations (non-GET requests)
|
||||
- Double-submit cookie pattern prevents CSRF attacks
|
||||
- Timing-safe comparison prevents timing attacks
|
||||
|
||||
### Session Fixation Prevention
|
||||
- Session rotation after authentication events
|
||||
- Old sessions invalidated when new ones created
|
||||
|
||||
### Secure Defaults
|
||||
- Secure flag enabled in production (requires HTTPS)
|
||||
- SameSite=Lax prevents most CSRF attacks
|
||||
- HttpOnly cookies prevent XSS token theft
|
||||
|
||||
## Development vs Production
|
||||
|
||||
### Development
|
||||
- `secure: false` - allows cookies over HTTP (localhost)
|
||||
- `NODE_ENV !== 'production'` detection
|
||||
|
||||
### Production
|
||||
- `secure: true` - requires HTTPS
|
||||
- All security flags enabled
|
||||
|
||||
## API Reference
|
||||
|
||||
### Key Functions (`src/server/lib/auth.ts`)
|
||||
|
||||
#### `generateCSRFToken(): string`
|
||||
Creates a cryptographically secure random token using `crypto.randomBytes(32).toString('hex')`.
|
||||
|
||||
#### `getSessionFromCookies(c: Context): Promise<Session | null>`
|
||||
Extracts sessionId from cookies, validates session exists and hasn't expired.
|
||||
|
||||
#### `validateCSRFToken(c: Context, sessionId: string): Promise<void>`
|
||||
Validates CSRF token from `X-CSRF-Token` header against session token using timing-safe comparison.
|
||||
|
||||
#### `assertOwner(c: Context): Promise<void>`
|
||||
Validates user has owner role and session is valid. Automatically validates CSRF token for non-GET requests.
|
||||
|
||||
#### `rotateSession(oldSessionId: string, userId: string): Promise<Session>`
|
||||
Creates new session with new ID and CSRF token, deletes old session.
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Existing Password Hashes
|
||||
- Base64 password hashes automatically migrated to bcrypt on server startup
|
||||
- No manual intervention required
|
||||
|
||||
### Session Invalidation
|
||||
- Old localStorage sessions will be invalidated
|
||||
- Users need to re-login once after deployment
|
||||
|
||||
### Testing Migration
|
||||
1. Deploy new version
|
||||
2. Verify login creates cookies (check browser DevTools)
|
||||
3. Test CSRF protection by manually calling API without token
|
||||
4. Verify session rotation after password change
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cookies Not Being Sent
|
||||
- Check `credentials: 'include'` in fetch configuration
|
||||
- Verify CORS settings allow credentials
|
||||
- Check cookie domain/path configuration
|
||||
|
||||
### CSRF Validation Failing
|
||||
- Ensure `X-CSRF-Token` header is set
|
||||
- Verify CSRF cookie is accessible to JavaScript
|
||||
- Check token format (64-character hex string)
|
||||
|
||||
### Session Expired Errors
|
||||
- Check cookie expiration settings
|
||||
- Verify server time synchronization
|
||||
- Check session cleanup logic
|
||||
|
||||
### Cross-Origin Issues
|
||||
- Review CORS configuration for credentials
|
||||
- Ensure domain configuration matches deployment
|
||||
- Check SameSite cookie settings
|
||||
|
||||
## Future Scaling
|
||||
|
||||
For multi-instance deployments, see [Redis Migration Guide](redis-migration.md) for migrating to centralized session storage.
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
```bash
|
||||
# Domain Configuration
|
||||
DOMAIN=localhost:5173 # For production: your-domain.com
|
||||
# Note: Session cookies are scoped to this domain
|
||||
|
||||
# Server Configuration
|
||||
NODE_ENV=development # Set to 'production' for production deployment
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
### Optional Environment Variables
|
||||
|
||||
```bash
|
||||
# Redis Configuration (for multi-instance deployments)
|
||||
# See docs/redis-migration.md for migration guide
|
||||
REDIS_URL=redis://localhost:6379
|
||||
REDIS_TLS_ENABLED=false # Set to true for production
|
||||
REDIS_PASSWORD=your_redis_password # Optional
|
||||
```
|
||||
|
||||
### Cookie Behavior by Environment
|
||||
|
||||
- **Development** (`NODE_ENV !== 'production'`):
|
||||
- `secure: false` - cookies work over HTTP
|
||||
- `sameSite: 'Lax'` - allows cross-site navigation
|
||||
|
||||
- **Production** (`NODE_ENV === 'production'`):
|
||||
- `secure: true` - requires HTTPS
|
||||
- `sameSite: 'Lax'` - prevents most CSRF attacks
|
||||
|
||||
## References
|
||||
|
||||
- [OWASP CSRF Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
|
||||
- [MDN HttpOnly Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
|
||||
- [RFC 6265 - HTTP State Management](https://tools.ietf.org/html/rfc6265)
|
Reference in New Issue
Block a user