7.8 KiB
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:
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:
- Server-side: CSRF token generated and stored in session
- Client-side: Same token stored in non-HttpOnly cookie
- Validation: Token from
X-CSRF-Token
header must match session token - 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
)
// 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 tokengetSessionFromCookies(c)
: Extracts and validates session from cookiesvalidateCSRFToken(c, sessionId)
: Validates CSRF token from headerassertOwner(c)
: Validates owner role with session and CSRF checksrotateSession(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
)
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
- Deploy new version
- Verify login creates cookies (check browser DevTools)
- Test CSRF protection by manually calling API without token
- 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 for migrating to centralized session storage.
Environment Configuration
Required Environment Variables
# 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
# 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 HTTPsameSite: 'Lax'
- allows cross-site navigation
-
Production (
NODE_ENV === 'production'
):secure: true
- requires HTTPSsameSite: 'Lax'
- prevents most CSRF attacks