Skip to content

feat: implement CSRF protection using server-side tokens #WP-186#1

Merged
dvrd merged 5 commits into
mainfrom
feat/csrf-protection
Feb 2, 2026
Merged

feat: implement CSRF protection using server-side tokens #WP-186#1
dvrd merged 5 commits into
mainfrom
feat/csrf-protection

Conversation

@dvrd

@dvrd dvrd commented Feb 2, 2026

Copy link
Copy Markdown
Owner

Implements Cross-Site Request Forgery (CSRF) protection following industry best
practices and security guidelines:

Implementation Details

Architecture

  • Token Generation: Cryptographically secure random tokens (256 bits) generated
    per session using crypto/rand
  • Server-Side Storage: Tokens stored in PostgreSQL sessions table tied to user
    sessions for immediate revocation capability
  • Constant-Time Verification: Uses hmac.Equal() to prevent timing attacks

Components Added

  1. internal/security/csrf.go: Token manager for secure random generation

    • Generates 64-character hex tokens (256 bits)
    • No database lookups during verification (done by middleware)
  2. internal/middleware/csrf.go: CSRF validation middleware

    • Validates on state-changing requests (POST, PUT, DELETE, PATCH)
    • Skips safe methods (GET, HEAD, OPTIONS)
    • Exempts endpoints: /health, /metrics, /ws/* (WebSocket uses different auth)
    • Extracts tokens from: form data, X-CSRF-Token header, X-XSRF-Token header
    • Logs security events on validation failure with context (user_id, method, path)
  3. internal/middleware/csrf_test.go: Comprehensive middleware tests

    • Tests token validation from multiple sources
    • Tests security event logging
    • Tests exemption of safe methods and endpoints

Database Changes

  • Migration 000003: Adds csrf_token column to sessions table
    • Unique constraint prevents token reuse across sessions
    • Index on csrf_token for efficient validation

Session Management

  • Session struct now includes CSRFToken field
  • SessionRepository extended with:
    • GetByCSRFToken(): Retrieve session by CSRF token
    • UpdateCSRFToken(): Associate token with session

Auth Handler Integration

  • Generates CSRF token on successful login
  • Returns token in LoginResponse JSON
  • Token stored server-side for immediate revocation on logout

Frontend Integration

  • Login/Register: Store CSRF token in sessionStorage
    • sessionStorage chosen over localStorage (cleared on tab close)
    • Safer than localStorage (XSS vulnerable)
  • API Calls: Include token in X-CSRF-Token header for all state-changing requests
  • Helper function added to index.html for consistent token handling

Security Properties

Prevents cross-site request forgery from attacker-controlled domains
Tokens tied to session - invalidated on logout/session expiration
Stateless verification possible but implemented server-side for:

  • Immediate revocation capability
  • Better logging and monitoring
  • Scales to load-balanced deployments (shared PostgreSQL)
    Constant-time comparison prevents timing attacks
    Security event logging for monitoring and alerting
    SameSite cookie attribute already set (Lax mode)
    No token leakage - never in URLs or cookies alone

Complementary Protections

  • Rate limiting on auth endpoints (already implemented)
  • HTTPS enforcement (configure in deployment)
  • CORS policies (already implemented)
  • Session timeout (already implemented)

Testing

  • Unit tests for token generation (uniqueness, format, entropy)
  • Integration tests for middleware (validation, exemption, logging)
  • All existing tests updated and passing
  • Handler and service tests updated with mock CSRF methods

Notes

  • Implementation uses per-session tokens (simpler than per-request)
  • WebSocket endpoints exempt from CSRF (use session token in query param)
  • Tokens persist for session lifetime (no per-request rotation)

Implements Cross-Site Request Forgery (CSRF) protection following industry best
practices and security guidelines:

## Implementation Details

### Architecture
- **Token Generation**: Cryptographically secure random tokens (256 bits) generated
  per session using crypto/rand
- **Server-Side Storage**: Tokens stored in PostgreSQL sessions table tied to user
  sessions for immediate revocation capability
- **Constant-Time Verification**: Uses hmac.Equal() to prevent timing attacks

### Components Added
1. **internal/security/csrf.go**: Token manager for secure random generation
   - Generates 64-character hex tokens (256 bits)
   - No database lookups during verification (done by middleware)

2. **internal/middleware/csrf.go**: CSRF validation middleware
   - Validates on state-changing requests (POST, PUT, DELETE, PATCH)
   - Skips safe methods (GET, HEAD, OPTIONS)
   - Exempts endpoints: /health, /metrics, /ws/* (WebSocket uses different auth)
   - Extracts tokens from: form data, X-CSRF-Token header, X-XSRF-Token header
   - Logs security events on validation failure with context (user_id, method, path)

3. **internal/middleware/csrf_test.go**: Comprehensive middleware tests
   - Tests token validation from multiple sources
   - Tests security event logging
   - Tests exemption of safe methods and endpoints

### Database Changes
- Migration 000003: Adds csrf_token column to sessions table
  - Unique constraint prevents token reuse across sessions
  - Index on csrf_token for efficient validation

### Session Management
- Session struct now includes CSRFToken field
- SessionRepository extended with:
  - GetByCSRFToken(): Retrieve session by CSRF token
  - UpdateCSRFToken(): Associate token with session

### Auth Handler Integration
- Generates CSRF token on successful login
- Returns token in LoginResponse JSON
- Token stored server-side for immediate revocation on logout

### Frontend Integration
- Login/Register: Store CSRF token in sessionStorage
  - sessionStorage chosen over localStorage (cleared on tab close)
  - Safer than localStorage (XSS vulnerable)
- API Calls: Include token in X-CSRF-Token header for all state-changing requests
- Helper function added to index.html for consistent token handling

## Security Properties

✓ **Prevents cross-site request forgery** from attacker-controlled domains
✓ **Tokens tied to session** - invalidated on logout/session expiration
✓ **Stateless verification possible** but implemented server-side for:
  - Immediate revocation capability
  - Better logging and monitoring
  - Scales to load-balanced deployments (shared PostgreSQL)
✓ **Constant-time comparison** prevents timing attacks
✓ **Security event logging** for monitoring and alerting
✓ **SameSite cookie** attribute already set (Lax mode)
✓ **No token leakage** - never in URLs or cookies alone

## Complementary Protections
- Rate limiting on auth endpoints (already implemented)
- HTTPS enforcement (configure in deployment)
- CORS policies (already implemented)
- Session timeout (already implemented)

## Testing
- Unit tests for token generation (uniqueness, format, entropy)
- Integration tests for middleware (validation, exemption, logging)
- All existing tests updated and passing
- Handler and service tests updated with mock CSRF methods

## Notes
- Implementation uses per-session tokens (simpler than per-request)
- WebSocket endpoints exempt from CSRF (use session token in query param)
- Tokens persist for session lifetime (no per-request rotation)
@dvrd dvrd self-assigned this Feb 2, 2026
@dvrd
dvrd force-pushed the feat/csrf-protection branch 2 times, most recently from ea62b71 to 47452af Compare February 2, 2026 22:48
Implements comprehensive CI/CD pipeline for automated quality checks:

Runs on:
- Push to main, develop, and feat/** branches

1. **Test** - Runs go test with race detector and coverage
   - Uses PostgreSQL 15 service for database tests
   - Reports coverage to codecov
   - Parallel test execution with -race flag

2. **Lint** - Runs golangci-lint
   - Checks code quality with strict rules
   - Detects issues like unused variables, incorrect error handling
   - Enforces security best practices

3. **Format Check** - Verifies code formatting
   - gofmt compliance (simplified syntax -s)
   - goimports import organization
   - Fails if formatting incorrect (auto-fixable with make fmt-fix)

4. **Vet** - Runs go vet
   - Static analysis for suspicious code
   - Type checking and common mistakes
   - No-op code detection

5. **Build** - Builds both binaries
   - chat-server build
   - stock-bot build
   - Ensures code compiles successfully
@dvrd
dvrd force-pushed the feat/csrf-protection branch from 47452af to faa19b9 Compare February 2, 2026 23:14
dvrd added 3 commits February 3, 2026 00:17
- Add pre-push hook to run linting before pushes
- Configure git to use .githooks directory for all team members
- Add setup-hooks.sh script for easy hook installation
- Ensures code quality standards are enforced locally
- Fix unused struct fields (remove unused mu sync.Mutex)
- Fix error shadowing by renaming duplicate error variables
- Fix unchecked error returns with _ assignments or proper handling
- Fix integer overflow in bit shift operations
- Fix string comparison with empty string instead of len()
- Combine identical parameter types in function signatures
- Add nolint comments for intentional warnings (exitAfterDefer, gosec)
- Add test helper annotations (nolint:unused) for mock structures
- Fix empty branch in switch statement by using blank identifier
- Remove unused helper functions (createMockCSV)
- Refactor if-else chains to switch statements where appropriate

All 124 initial linting errors resolved.
- Replace deprecated 'golint' linter with 'revive'
- Remove deprecated linters from disabled list (exhaustivestruct, gomnd)
- Remove redundant enabled checks in gocritic (use defaults)
- Simplify configuration to only specify custom checks

Resolves all golangci-lint warnings. Clean output with no warnings or errors.
@dvrd
dvrd merged commit 40cb156 into main Feb 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant