Session Generation

Deep dive into Narrowbeam's privacy-preserving session generation algorithm. Understand the technical details of how we create sessions without cookies or user tracking.

The Algorithm

Narrowbeam generates session IDs server-side using a deterministic hashing algorithm:

// Step 1: Calculate current 4-hour time bucket
const FOUR_HOURS_MS = 4 * 60 * 60 * 1000; // 14,400,000ms
const now = Date.now();
const timeBucket = Math.floor(now / FOUR_HOURS_MS) * FOUR_HOURS_MS;

// Step 2: Combine inputs
const input = `${origin}:${ip}:${userAgent}:${timeBucket}`;

// Step 3: Hash with SHA-256
const sessionId = await sha256(input);

// Result: 64-character hex string
// Example: "a1b2c3d4e5f6...xyz789"

Algorithm Inputs

1. Origin (Website Domain)

The origin ensures sessions are site-specific:

  • Example: https://yoursite.com
  • Prevents cross-site session correlation
  • Each domain has independent sessions

2. IP Address

The visitor's IP address distinguishes between different users:

  • Obtained from network request headers
  • Only used for hashing, never stored
  • Cannot be extracted from the hash
  • Changes when network changes (mobile switching to WiFi)
Privacy guarantee: IP addresses are NEVER stored in our database. They exist only in memory during the request for hashing purposes.

3. User Agent

The browser's User-Agent string differentiates devices/browsers:

  • Example: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...
  • Distinguishes between browsers on same network
  • Same user, different browser = different session

4. Time Bucket (4-Hour Window)

Time is quantized into 4-hour buckets aligned to midnight UTC:

BucketUTC Time RangeTimestamp Example
100:00:00 - 03:59:591730937600000
204:00:00 - 07:59:591730952000000
308:00:00 - 11:59:591730966400000
412:00:00 - 15:59:591730980800000
516:00:00 - 19:59:591730995200000
620:00:00 - 23:59:591731009600000

The time bucket calculation:

// Example: Current time = 2024-11-07 10:30:15 UTC
now = 1730980215000 // milliseconds since epoch
FOUR_HOURS_MS = 14400000

// Divide and floor to get bucket start
timeBucket = Math.floor(1730980215000 / 14400000) * 14400000
timeBucket = 120207 * 14400000
timeBucket = 1730980800000 // Bucket 3: 08:00-11:59

SHA-256 Hashing

The combined input is hashed using SHA-256, a cryptographic hash function:

Properties of SHA-256

  • One-way: Cannot reverse hash to get original inputs
  • Deterministic: Same input always produces same hash
  • Collision-resistant: Virtually impossible to find two inputs with same hash
  • Avalanche effect: Small input change = completely different hash

Example Hash Generation

Input: https://example.com:192.0.2.1:Mozilla/5.0...:1730980800000
Hash:  7a3f8c9e2d1b4e6f8a9c0d2e4f6a8b0c1d3e5f7a9b0c2d4e6f8a9c0d2e4f6a8b

Input: https://example.com:192.0.2.1:Mozilla/5.0...:1730995200000 (next bucket)
Hash:  4e6f8a9c0d2e4f6a8b0c1d3e5f7a9b0c2d4e6f8a9c0d2e4f6a8b7a3f8c9e2d1b

Notice: Same user, different time bucket = completely different hash

Why This Approach is Privacy-Preserving

1. No Individual Tracking

Sessions expire after 4 hours maximum. There's no way to track a user across days or weeks.

2. Cannot Identify Users

The hash cannot be reversed to obtain IP addresses or other identifying information.

3. No Persistent Identifiers

Unlike cookies or localStorage IDs, session IDs change every 4 hours automatically.

4. IP Addresses Never Stored

IP addresses exist only in request headers and memory during hashing. They never touch the database.

5. Deterministic but Anonymous

Same inputs = same session ID (allowing session grouping), but the hash reveals nothing about the user.

Session Behavior Examples

Example 1: Same User, Same Bucket

🕐 10:30 AM: User visits site → Session ABC
🕐 11:15 AM: User returns → Session ABC (same)
   Reason: Same IP, browser, and 4-hour bucket (08:00-11:59)

Example 2: Same User, New Bucket

🕐 11:30 AM: User visits site → Session ABC
🕐 12:30 PM: User returns → Session XYZ (different)
   Reason: Crossed into new bucket (12:00-15:59)

Example 3: Different Network

📱 On WiFi: User visits → Session ABC
📱 On Mobile: User visits → Session XYZ (different)
   Reason: IP address changed (WiFi vs cellular)

Example 4: Different Browser

🌐 Chrome: User visits → Session ABC
🌐 Safari: User visits → Session XYZ (different)
   Reason: User-Agent string differs

Implementation Code

The actual implementation in lib/session-id.ts:

export async function generateSessionId(
  origin: string,
  ip: string,
  userAgent: string
): Promise<string> {
  const now = Date.now();
  const timeBucket = Math.floor(now / FOUR_HOURS_MS) * FOUR_HOURS_MS;

  const input = `${origin}:${ip}:${userAgent}:${timeBucket}`;
  return await sha256(input);
}

Comparison to Cookie-Based Sessions

AspectCookie-BasedNarrowbeam (Hash-Based)
Storage LocationBrowser (cookie)None (computed on request)
User Can ClearYes (affects tracking)N/A (nothing stored)
Cross-BrowserNoNo (intentional)
Max DurationYears (if configured)4 hours (automatic)
Privacy RiskHigh (long-term tracking)Low (time-limited, anonymous)
Consent RequiredOften yesUsually no

Security Considerations

Hash Collisions

SHA-256 has a hash space of 2^256, making collisions astronomically unlikely. Even with billions of sessions, collision probability is negligible.

Rainbow Table Attacks

Rainbow tables (precomputed hashes) are impractical because:

  • IP addresses vary widely
  • User-Agent strings have many variations
  • Time buckets change every 4 hours
  • Origin is site-specific

Session Hijacking

Session IDs are generated server-side and not transmitted to clients, preventing session hijacking attacks.

Related Documentation