Skip to content

SECURITY: Fix token refresh race condition (nb-6yd)#3

Merged
ebrett merged 2 commits intomainfrom
feature/nb-024-fix-token-refresh-race-condition
Dec 4, 2025
Merged

SECURITY: Fix token refresh race condition (nb-6yd)#3
ebrett merged 2 commits intomainfrom
feature/nb-024-fix-token-refresh-race-condition

Conversation

@ebrett
Copy link
Owner

@ebrett ebrett commented Dec 2, 2025

Summary

  • CRITICAL SECURITY FIX: Thread-safe token refresh using Monitor synchronization
  • Prevents authentication failures in multi-threaded environments
  • Implements double-check locking pattern for optimal performance
  • Comprehensive concurrency test coverage

Problem

The ensure_fresh_token! method in HttpClient was not thread-safe. In multi-threaded environments (Puma, Sidekiq), multiple threads could detect an expired token simultaneously and all attempt to refresh it, causing:

  • Multiple wasteful OAuth refresh requests
  • Authentication failures due to race conditions
  • Increased risk of hitting rate limits
  • Potential token invalidation issues

Solution

Added Monitor (reentrant mutex) synchronization with double-check locking:

Fast Path (no lock)

  • Check if token is expired
  • If fresh, return immediately without locking
  • This is the common case and has minimal overhead

Slow Path (with lock)

  • Acquire mutex lock
  • Double-check token expiry (another thread may have refreshed it)
  • Only refresh if still expired
  • Ensures only one thread performs the actual refresh

Implementation Details

def ensure_fresh_token!
  token_data = @token_adapter.retrieve_token(@identifier)
  return unless token_data

  # Fast path: check without locking
  return unless OAuth.token_expired?(token_data[:expires_at])

  # Slow path: acquire lock and double-check
  @token_refresh_mutex.synchronize do
    token_data = @token_adapter.retrieve_token(@identifier)
    return unless token_data
    return unless OAuth.token_expired?(token_data[:expires_at])

    refresh_token!(token_data[:refresh_token])
  end
end

Testing

New Concurrency Test Suite

Created spec/nationbuilder_api/http_client_concurrency_spec.rb:

Test 1: Verifies mutex prevents race condition

  • Launches 10 threads with expired token
  • All threads detect expiry simultaneously
  • Result: Only 1 OAuth refresh call (not 10)

Test 2: Verifies normal requests remain concurrent

  • Launches 10 threads with fresh token
  • Result: Requests execute concurrently (no serialization)

All tests passing: 2 new examples, 0 failures

Security Impact

Before: Race condition could cause authentication failures in production
After: Thread-safe token refresh, reliable in multi-threaded environments

Performance Impact

  • Negligible overhead for fresh tokens (fast path, no locking)
  • Minimal overhead for expired tokens (one lock acquisition)
  • No serialization of normal requests (only token refresh is synchronized)

References

  • Fixes: nb-6yd (CRITICAL priority)
  • Related: Comprehensive security audit findings
  • Pattern: Double-checked locking

🤖 Generated with Claude Code

ebrett and others added 2 commits December 4, 2025 10:02
Added Monitor-based synchronization to prevent multiple threads from
simultaneously refreshing the same OAuth token, which was causing
authentication failures in multi-threaded environments (Puma, Sidekiq).

Implementation:
- Added Monitor (reentrant mutex) to HttpClient for thread-safe token refresh
- Implemented double-check locking pattern for optimal performance:
  - Fast path: Check token expiry without locking (most common case)
  - Slow path: Acquire lock and recheck before refreshing
- Only one thread can refresh a token at a time per HttpClient instance

Testing:
- Added comprehensive concurrency test suite (http_client_concurrency_spec.rb)
- Verifies mutex prevents race condition (10 threads → 1 refresh call)
- Verifies normal requests remain concurrent when token is fresh
- All tests passing

Security Impact:
- Prevents authentication failures in production multi-threaded apps
- Eliminates wasted OAuth refresh requests
- Reduces risk of rate limiting due to duplicate refreshes

Fixes: nb-6yd

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ebrett ebrett force-pushed the feature/nb-024-fix-token-refresh-race-condition branch from 956aee8 to dd09632 Compare December 4, 2025 03:03
@ebrett ebrett merged commit f849f2b into main Dec 4, 2025
3 checks passed
@ebrett ebrett deleted the feature/nb-024-fix-token-refresh-race-condition branch December 10, 2025 05:52
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