Server-Sent Events (SSE) Explained and Secured
In this article, I'll explain Server-Sent Events (SSE), one of the technologies for implementing real-time data communication in web applications. We'll explore the basic concepts and operational mechanisms of SSE, compare it with other technologies, and discuss security enhancement measures that must be considered in production environments.
Server-Sent Events is, as the name suggests, a technology where the server sends events to the client. When a browser requests a connection to the server, it maintains that connection while the server asynchronously pushes necessary data to the client in a one-way communication method.
SSE operates based on the HTTP protocol, which offers the advantage of utilizing existing web infrastructure without implementing additional protocols. Its main purpose is real-time data streaming from server to client, widely used for notifications, real-time feeds, status updates, and more.
The operating principle of SSE is relatively simple. The interaction between client and server follows this flow:
EventSource object.Content-Type header to text/event-stream. This header informs the client that the data to be transmitted will be an event stream. sequenceDiagram
participant Client
participant Server
Client->>Server: new EventSource('/events')
Note over Client,Server: Initial HTTP Request
Server-->>Client: HTTP 200 OK<br>Content-Type: text/event-stream<br>Connection: keep-alive
Note over Client,Server: Connection Established
loop Real-time Events
Server-->>Client: data: Some new data\n\n
Server-->>Client: event: notification<br>data: {"user":"guest","message":"Welcome!"}\n\n
Server-->>Client: data: Another update\n\n
end
Client->>Server: eventSource.close()
Note over Client,Server: Connection Closed by Client
The data transmitted by the server is a simple text stream, with each message separated by one or more key: value fields and two newline characters (\n\n). The main fields include:
data: The actual data to be transmitted. Multiple data fields can be included in one message.event: A field that specifies the type of event. Clients can use this value to listen for specific types of events. If not specified, it's processed as a message event.id: A unique ID for each event. When a client reconnects to the server, it can send the id of the last received event in the Last-Event-ID header to recover any lost data.retry: Specifies the time in milliseconds that the client should wait before attempting to reconnect.// Simple message
data: This is a message.
// JSON data with a custom event type
event: user_update
data: {"username": "hahwul", "status": "online"}
// Message with an ID for synchronization
id: msg1
data: Some data stream
Besides SSE, there are other technologies for implementing real-time web communications such as Polling, Long-Polling, and WebSockets. Each technology has distinct characteristics and pros and cons, so you should choose the appropriate one based on the problem you're trying to solve.
| Feature | Polling | Long-Polling | SSE (Server-Sent Events) | WebSockets |
|---|---|---|---|---|
| Direction | Client -> Server | Client -> Server | Server -> Client (Unidirectional) | Bidirectional |
| Protocol | HTTP | HTTP | HTTP | WebSocket (ws://, wss://) |
| Connection | New connection per request | Long-lived, then new | Single persistent connection | Single persistent connection |
| Overhead | High | Medium | Low | Low (after handshake) |
| Use Case | Infrequent updates | Delayed updates | Notifications, Live Feeds | Chat, Gaming, Collaboration |
| Reconnection | Manual | Manual | Automatic (built-in) | Manual |
EventSource API has built-in automatic reconnection features, making it reliable.Client-side implementation can be written very simply through the EventSource API.
const eventSource = new EventSource('/stream');
// General message listener
eventSource.onmessage = (event) => {
console.log('New message:', event.data);
};
// Listener for custom events
eventSource.addEventListener('notification', (event) => {
const notificationData = JSON.parse(event.data);
console.log('Notification:', notificationData.message);
});
// Error handling
eventSource.onerror = (err) => {
console.error("EventSource failed:", err);
// EventSource will automatically try to reconnect.
// To close it permanently:
// eventSource.close();
};
Since SSE operates over HTTP, it is exposed to common web security threats. Therefore, when using SSE in a production environment, security must be considered.
The EventSource API does not standardly support setting custom HTTP headers like Authorization. This creates constraints for typical token-based authentication.
EventSource request, so session cookie-based authentication works naturally. This is one of the simplest and most effective methods./stream?token=...). However, this method risks exposing the token in server logs, browser history, etc., so it should be used cautiously.SSE connections start with a GET request, so they can be vulnerable to CSRF attacks. An attacker could trick a user into visiting a malicious page while logged in, establishing an SSE connection using the user's privileges and stealing sensitive real-time data.
Origin header of requests to allow only those from authorized domains.SameSite=Lax or SameSite=Strict attribute on authentication cookies prevents cookies from being sent when requests come from different origins.If data sent by the server is rendered directly to HTML on the client side, an XSS vulnerability can occur. Data received from the server should never be trusted and should always be escaped or encoded before use.
const outputDiv = document.getElementById('output');
eventSource.onmessage = (event) => {
// Vulnerable to XSS: Never do this!
// outputDiv.innerHTML += event.data + '<br>';
// Safe: Use textContent to render data as plain text.
const p = document.createElement('p');
p.textContent = event.data;
outputDiv.appendChild(p);
};
To prevent the exposure or modification of event streams through man-in-the-middle (MITM) attacks, SSE communications must be encrypted via HTTPS. This is not optional but essential.
SSE maintains connections for extended periods, making it a potential target for denial of service attacks. An attacker could create numerous connections to deplete the server's resources.
Server-Sent Events (SSE) is a powerful and standardized technology for real-time data streaming from server to client. It's easy to implement as it's based on HTTP, and it has built-in convenience features like automatic reconnection, making development and maintenance convenient.
However, behind this convenience, there are security aspects that must be considered. For secure SSE implementation, it's crucial to recognize various threats such as authentication/authorization, CSRF, XSS, DoS, and apply appropriate defense mechanisms.
Unless bidirectional communication is essential, SSE is a highly efficient alternative that can implement real-time update functionality without the complexity of WebSockets.