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)
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:
| Bucket | UTC Time Range | Timestamp Example |
|---|---|---|
| 1 | 00:00:00 - 03:59:59 | 1730937600000 |
| 2 | 04:00:00 - 07:59:59 | 1730952000000 |
| 3 | 08:00:00 - 11:59:59 | 1730966400000 |
| 4 | 12:00:00 - 15:59:59 | 1730980800000 |
| 5 | 16:00:00 - 19:59:59 | 1730995200000 |
| 6 | 20:00:00 - 23:59:59 | 1731009600000 |
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:59SHA-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: 4e6f8a9c0d2e4f6a8b0c1d3e5f7a9b0c2d4e6f8a9c0d2e4f6a8b7a3f8c9e2d1bNotice: 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 differsImplementation 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
| Aspect | Cookie-Based | Narrowbeam (Hash-Based) |
|---|---|---|
| Storage Location | Browser (cookie) | None (computed on request) |
| User Can Clear | Yes (affects tracking) | N/A (nothing stored) |
| Cross-Browser | No | No (intentional) |
| Max Duration | Years (if configured) | 4 hours (automatic) |
| Privacy Risk | High (long-term tracking) | Low (time-limited, anonymous) |
| Consent Required | Often yes | Usually 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.