๐Ÿ“ก Chapter 51: Server-Sent Events (SSE) & Real-Time Streaming

SSE Security, CORS & Credentials

Mastering Cross-Origin Resource Sharing (CORS), `withCredentials`, authorization token patterns, Content Security Policy (`connect-src`), and XSS data sanitization.

LEARNING OBJECTIVES โŒต
  • Understand the Same-Origin Policy (SOP) and Cross-Origin Resource Sharing (CORS) rules governing EventSource.
  • Configure withCredentials: true for cross-origin cookie-based session authentication.
  • Overcome the EventSource custom HTTP header limitation using ephemeral ticket authentication.
  • Configure Content Security Policy (CSP) connect-src directives to permit real-time streaming endpoints.
  • Sanitize streaming text payloads to prevent stored and reflected Cross-Site Scripting (XSS) attacks.
๐ŸŽฌ INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
๐ŸŒ
1. Input
Directives & Tags
โš™๏ธ
2. Parse
Tokenizer & AST
๐ŸŒณ
3. Layout
Box Model & Flow
๐ŸŽจ
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

๐Ÿ“– The Mental Model & Story (Intuitive Foundation)

Imagine entering an ultra-secure corporate research building.

Scenario A: Same-Origin (Inside Your Own Department)

You work on the 4th floor (https://app.company.com). You walk into the 4th-floor cafeteria (https://app.company.com/stream). The receptionist recognizes your face immediately. No special passport checks are needed.

Frontend: https://app.company.com   ----->   Backend: https://app.company.com/stream
                             [ SAME-ORIGIN: AUTOMATIC TRUST ]

Scenario B: Cross-Origin without Credentials (withCredentials: false)

You visit the external partner building across the street (https://api.partner.com/stream). You are allowed to listen to the public lobby TV announcements, but you do not show your employee ID badge. The partner building must post a sign: Access-Control-Allow-Origin: *.

Frontend: https://app.company.com   ----->   Backend: https://api.partner.com/stream
                     [ CROSS-ORIGIN (No Badge / No Cookies) ]
                     [ Server must reply: Access-Control-Allow-Origin: * ]

Scenario C: Cross-Origin with Credentials (withCredentials: true)

You visit the executive partner boardroom across the street and want to access private company data. You flash your official employee badge (HttpOnly Session Cookie).

  • The Security Rule: The partner building cannot just allow everyone (*). They must explicitly verify your exact domain (Access-Control-Allow-Origin: https://app.company.com) and confirm badge access (Access-Control-Allow-Credentials: true).
Frontend: https://app.company.com   ===== Badge / Cookies =====> Backend: https://api.partner.com
    [ Server verifies exact Origin: https://app.company.com + Allow-Credentials: true ]

Technical Deep Dive & Specifications

1. The CORS Protocol Requirements for EventSource

When connecting to a cross-origin SSE stream:

// Cross-Origin connection with Cookies / Credentials
const sse = new EventSource('https://api.example.com/live-stream', {
  withCredentials: true
});

The server must return the following HTTP response headers:

HTTP/1.1 200 OK
Content-Type: text/event-stream; charset=utf-8
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Connection: keep-alive

[!CAUTION] If withCredentials: true is set, the server MUST NOT send Access-Control-Allow-Origin: *. The browser's CORS security policy will reject the connection immediately with a security error.

2. The Custom Header Dilemma & Solutions

The native EventSource API does not support custom request headers (like Authorization: Bearer <jwt>).

// โŒ INVALID - The browser silently ignores any headers object!
const sse = new EventSource('/stream', {
  headers: { 'Authorization': 'Bearer my-jwt-token' } 
});

To authenticate SSE streams in production, choose one of these three battle-tested architecture patterns:

+-----------------------------------------------------------------------------------------------+
|                               SSE AUTHENTICATION STRATEGIES                                   |
+-----------------------------------------------------------------------------------------------+
|  Strategy                     | Mechanism                                | Security Profile   |
+-------------------------------+------------------------------------------+--------------------+
|  1. HttpOnly Session Cookie   | Uses `withCredentials: true`             | โญ High (Prevents  |
|     (Recommended for Web)     | Cookies sent automatically over HTTPS    | XSS token theft)   |
+-------------------------------+------------------------------------------+--------------------+
|  2. Single-Use Ticket / Token | Client POSTs for short-lived ticket;     | โญ High (Avoids    |
|     (For API Microservices)   | Connects via `?ticket=<single_use_uuid>` | URL token logging) |
+-------------------------------+------------------------------------------+--------------------+
|  3. Fetch + ReadableStream    | Uses `fetch()` with custom headers       | โญ Moderate-High   |
|     (Polyfill Approach)       | Parses stream chunks manually in JS      | (Requires manual   |
|                               |                                          | reconnect logic)   |
+-------------------------------+------------------------------------------+--------------------+

The Ephemeral Ticket Pattern (Strategy 2 Walkthrough)

  1. Client sends authenticated REST call: POST /api/auth/sse-ticket with Authorization: Bearer <token>.
  2. Backend generates a 30-second single-use cryptographically random ticket (stored in Redis).
  3. Backend returns { ticket: "tkt_8f9a2b..." }.
  4. Client opens stream: new EventSource('/api/stream?ticket=tkt_8f9a2b...').
  5. Backend validates ticket, burns it from Redis immediately, and keeps the stream open.
CLIENT                                REST API                               SSE GATEWAY
  |                                       |                                       |
  |--- 1. POST /auth/sse-ticket (Bearer)->|                                       |
  |<-- 2. HTTP 200 { ticket: "xyz" } -----|                                       |
  |                                                                               |
  |--- 3. new EventSource('/stream?ticket=xyz') --------------------------------->|
  |                                                                               | (Validates & Burns xyz)
  |<-- 4. HTTP 200 text/event-stream (data: ...) ---------------------------------|

3. Content Security Policy (CSP) Configuration

If your web application enforces a strict Content Security Policy, you must explicitly permit the SSE streaming origin under the connect-src directive:

Content-Security-Policy: default-src 'self'; connect-src 'self' https://api.example.com https://realtime.example.com;

4. Preventing Cross-Site Scripting (XSS) in Real-Time UIs

Because SSE payloads arrive as plain text, injecting raw event.data directly into innerHTML allows malicious users to execute arbitrary JavaScript on other users' screens:

// โŒ VULNERABLE TO ZERO-CLICK STORED XSS!
sse.onmessage = (e) => {
  document.getElementById('chat').innerHTML += `<div>${e.data}</div>`;
};

// โœ… SECURE - Use textContent or DOM safe node creation
sse.onmessage = (e) => {
  const msgEl = document.createElement('div');
  msgEl.textContent = e.data; // Browser auto-escapes HTML/scripts
  document.getElementById('chat').appendChild(msgEl);
};

๐Ÿ’ป Interactive Code Playground

Below is an interactive SSE Security & Authentication Playground. It lets you experiment with CORS credentials validation, single-use ticket exchange, and XSS payload rendering safety.

Starter Code

Line-by-Line Code Breakdown

  • Lines 105โ€“118: Demonstrates the ephemeral ticket acquisition pattern: exchanging a long-lived JWT for a short-lived, single-use ticket before instantiating EventSource.
  • Lines 120โ€“128: Simulates the backend gateway validating the ticket and burning it from Redis upon first connection. Even if the URL is logged in proxy logs, the token cannot be reused.
  • Lines 135โ€“138 (btnRenderVulnerable): Illustrates the classic stored XSS vulnerability when developers assign element.innerHTML = event.data.
  • Lines 140โ€“143 (btnRenderSecure): Demonstrates the safe sanitization pattern using element.textContent, preventing script execution.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...
๐Ÿ”’ SSE Security, CORS & Authentication
+------------------------------------+------------------------------------+
| 1. Single-Use Ticket Handshake     | 2. XSS Payload Injection Defense   |
+------------------------------------+------------------------------------+
| [ POST /auth/sse-ticket ]          | [ Render innerHTML ] [ Safe text ] |
| [ Open EventSource(?ticket=...) ]  |                                    |
|                                    | Rendered Result:                   |
| Logs:                              | <img src=x onerror=...> Hello!     |
| [10:55:01] 200 OK: Generated tkt_x | (Safe string, not executed)        |
| [10:55:02] Stream OPEN             |                                    |
+------------------------------------+------------------------------------+

๐Ÿ‹๏ธ Hands-On Exercise

๐ŸŽฏ The Challenge: Build a Secure Ticket-Authenticated SSE Connector

Instructions:

  1. Create a function connectSecureStream(streamUrl, tokenEndpoint, jwtToken):
  2. Step 1: Execute fetch(tokenEndpoint, { method: 'POST', headers: { 'Authorization': 'Bearer ' + jwtToken } }) to obtain a ticket.
  3. Step 2: Parse { ticket: string } from the response.
  4. Step 3: Instantiate and return new EventSource(${streamUrl}?ticket=${ticket}).
  5. Include robust error handling for HTTP 401 Unauthorized responses.

๐Ÿ Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...

โš ๏ธ Common Pitfalls

  1. Passing Access-Control-Allow-Origin: * with withCredentials: true: The browser strictly forbids wildcard origins when credentials are requested. The server must echo the exact client origin: Access-Control-Allow-Origin: https://client.example.com.
  2. Placing Long-Lived JWTs in SSE Query Strings: Writing new EventSource('/stream?jwt=' + userJwt) exposes sensitive user credentials in access logs, CDN logs, and proxy traces. Always use short-lived ephemeral tickets or HttpOnly cookies.
  3. Rendering SSE Payloads via innerHTML: Streaming data frequently contains user-generated content (chat messages, logs). Assigning raw text directly to innerHTML introduces critical Cross-Site Scripting (XSS) vulnerabilities. Always sanitize or use textContent.

๐Ÿ’ก Pro Tips

  1. Token Refresh via Reconnect Ticket: When an authenticated SSE stream drops and reconnects, an expired URL ticket will cause an HTTP 401. To handle automatic reconnection with fresh tickets, use @microsoft/fetch-event-source or intercept reconnection with a custom token refresh wrapper.
  2. CSP connect-src Subdomain Wildcards: For large distributed streaming infrastructure, configure connect-src https://*.stream.yourdomain.com in your Content Security Policy to allow dynamic edge cluster routing.

๐Ÿ“Œ Key Takeaways

  • withCredentials: true enables sending HttpOnly session cookies across origins.
  • When withCredentials: true is enabled, Access-Control-Allow-Origin cannot be *.
  • EventSource does not support custom HTTP headers; use HttpOnly cookies or the Single-Use Ephemeral Ticket pattern.
  • Content Security Policy requires adding streaming domains to connect-src.
  • Always sanitize SSE text payloads before injecting into the DOM to prevent XSS vulnerabilities.
  • --
โญ LEARN: HTML ๐ŸŒŸ โš”๏ธ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Why will the browser block an EventSource connection configured with withCredentials: true if the server returns Access-Control-Allow-Origin: *?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

How can an application authenticate an EventSource connection when using Bearer JWT tokens without exposing long-lived tokens in query parameters?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

Which Content Security Policy (CSP) directive controls which domains an EventSource can connect to?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP