Files
beauty-bookings/docs/session-management.md

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
  • Type: HttpOnly, Secure (production), SameSite=Lax
  • Path: /
  • MaxAge: 86400 seconds (24 hours)
  • Purpose: Authenticates user across requests
  • 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 - 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)

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 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
  • 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