<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Aneesh Arora</title>
    <description>The latest articles on Forem by Aneesh Arora (@aneesh_arora).</description>
    <link>https://forem.com/aneesh_arora</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F916337%2F28d608c6-8106-4fea-aeeb-5a60ea7f0f8c.png</url>
      <title>Forem: Aneesh Arora</title>
      <link>https://forem.com/aneesh_arora</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/aneesh_arora"/>
    <language>en</language>
    <item>
      <title>Take the pain to learn user authentication before you use an external provider</title>
      <dc:creator>Aneesh Arora</dc:creator>
      <pubDate>Tue, 19 Mar 2024 09:01:37 +0000</pubDate>
      <link>https://forem.com/aneesh_arora/take-the-pain-to-learn-user-authentication-before-you-use-an-external-provider-299b</link>
      <guid>https://forem.com/aneesh_arora/take-the-pain-to-learn-user-authentication-before-you-use-an-external-provider-299b</guid>
      <description>&lt;p&gt;External user authentication is not as simple, secure and cheap as it seems. These are the lessons I learned while implementing user authentication and authorization for my startup &lt;a href="https://www.afterword.tech/"&gt;Afterword&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most startups nowadays rely on external user authentication services called &lt;strong&gt;"Identity as a Service" (IDaaS)&lt;/strong&gt; or &lt;strong&gt;"Authentication as a Service" (AuthaaS)&lt;/strong&gt;. Even big companies like &lt;a href="https://twitter.com/kwuchu/status/1641477407180824576"&gt;OpenAI use auth0&lt;/a&gt;. Firebase by Google is also really popular. While I guess it makes sense in the world of “move fast and break things” we have been sold a lot of lies about user authentication by these providers which I would like to dispel.&lt;/p&gt;

&lt;p&gt;Developers think outsourcing user authentication to an external provider is easier, more secure, cheaper in the short run but it’s not true. If you don’t understand security well enough you will still store important information like user authentication tokens in localstorage or cookies that can be read by client side javascript. Javascript injection attacks will allow hackers to steal user credentials and pretend to be them, exposing user data and consuming their credits in a Saas application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems with external user authentication services
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The False Security of Outsourcing:&lt;/strong&gt; Outsourcing user authentication does not automatically ensure security; poor implementation can leave users exposed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy Concerns with Third-Party Data Handling:&lt;/strong&gt; Utilizing third-party authentication involves entrusting user login data to another vendor, risking user privacy and data exploitation by competitors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control Over User Authentication Flow:&lt;/strong&gt; Third-party services limit customization and control the flow of user authentication, degrading user experience due to external redirections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design Constraints with Pre-Designed UI:&lt;/strong&gt; Pre-designed user interfaces from third-party services may not align with your site's aesthetics, causing a disjointed experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech Stack Compatibility Issues:&lt;/strong&gt; Third-party user authentication may not seamlessly integrate with your specific tech stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time Efficiency in Self-Implementation:&lt;/strong&gt; Self-implementation of user authentication can be quicker with proper understanding; my experience saw more than a week for third-party setup versus 4-5 days for in-house.

&lt;ul&gt;
&lt;li&gt;Cost of Third-Party Authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosted Services:&lt;/strong&gt; Costs escalate with user growth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-Hosting:&lt;/strong&gt; Adds complexity when deploying and contributes to extra costs also.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs of Advanced Features:&lt;/strong&gt; Extra features like social login and multi-factor authentication in external services incur additional expenses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Risks of Vendor Lock-In:&lt;/strong&gt; Dependence on third-party providers exposes you to their changing terms, costs, and risks of vendor lock-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenges in Transitioning to In-House Solutions:&lt;/strong&gt; Shifting to an in-house system later can be complex, often necessitating password changes for users, thus increasing friction and dissatisfaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The basics of user authentication and authorization
&lt;/h2&gt;

&lt;p&gt;User authentication and authorization may initially appear challenging and intimidating, but this perception changes once you grasp the underlying mechanisms. Moreover, it's an essential aspect you can't bypass. Despite popular belief, third-party user authentication isn't the panacea it's often made out to be.&lt;/p&gt;

&lt;p&gt;In the sections below, I'll guide you through a secure method for implementing user authentication and authorization. While there are other approaches, they fall beyond the scope of this blog post.&lt;/p&gt;

&lt;p&gt;First let’s understand the difference between user authentication and user authorization:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User Authentication:&lt;/strong&gt; Process of verifying identity of the user. Typically done using username and password.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Authorization:&lt;/strong&gt; Verifying the logged in user when they make a certain request like accessing their saved data or performing any action that the user should be signed in for.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Upon successful user authentication, that is, when they log in, the server issues a cookie containing a token(a unique string). This cookie is sent by the browser with each subsequent request, allowing the server to verify the user's identity and respond to their requests.&lt;/p&gt;

&lt;p&gt;There are two primary methods to verify this token:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Database Storage:&lt;/strong&gt; Store the token and its corresponding user ID (a unique identifier in the database akin to a primary key) in the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON Web Token (JWT):&lt;/strong&gt; Utilize JWT, a widely supported standard that I will explain below.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How JSON Web Token works
&lt;/h2&gt;

&lt;p&gt;Many libraries support JWT so you don’t have to implement it yourself but you do need to understand it. One good one for python is &lt;strong&gt;PyJWT.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;pip install PyJWT&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;JWTs are advantageous as they encapsulate user details, such as the user ID, along with other pertinent information chosen by the developer. This data is encrypted using a secret key and stored in the cookie. When the user makes a request, the server decrypts the JWT to identify the user and access their data.&lt;/p&gt;

&lt;p&gt;Benefits of using JWT include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed:&lt;/strong&gt; Eliminates the need for additional database queries to fetch the user ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in Expiration:&lt;/strong&gt; JWTs have an inherent expiry time, enhancing security by limiting the window during which a compromised token could be used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At Afterword, for instance, access tokens expire after one hour for added user safety.&lt;/p&gt;

&lt;p&gt;Example code to create and decode JWT in python using PyJWT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import jwt
from jwt import PyJWTError
from datetime import datetime, timedelta

SECRET_KEY = "Generate a secret key and put it here"
ALGORITHM = "HS256"
TOKEN_EXPIRE_MINUTES = 60

def create_jwt(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire}) 
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# Verify JWT token
def decode_token(token):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload

    except PyJWTError:
        raise Exception(status_code=401, detail="Could not validate credentials")

create_jwt({"user_id":user_id}) #you can also put other user data in this dict as key value pairs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To address the issue of token expiration and avoid frequent re-logins, which could detract from user experience, we employ a dual-token approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Access Token:&lt;/strong&gt; Short-lived, used for regular authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refresh Token:&lt;/strong&gt; Longer-lived, used to request new access tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The refresh token is only sent to the server when an access token is rejected, not with every request. This strategy minimizes the risk of a refresh token being compromised. For heightened security, the refresh token can be stored in the database and validated against it. Upon user logout, or to prevent misuse, refresh tokens can be either deleted or added to a blacklist, ensuring that outdated, yet valid, tokens cannot be used to gain unauthorized access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storing JWTs (access and refresh tokens) browser side
&lt;/h2&gt;

&lt;p&gt;Since &lt;strong&gt;local storage and other storage methods like IndexedDB&lt;/strong&gt; can be accessed by javascript running in the browser they are not a safe place to store user authorization credentials. Hackers can exploit it using &lt;strong&gt;Cross-Site Scripting (XSS)&lt;/strong&gt; to inject malicious code and access these tokens.&lt;/p&gt;

&lt;p&gt;An alternative to this is the use of &lt;strong&gt;cookies&lt;/strong&gt;. While regular cookies can be accessed by JavaScript, &lt;strong&gt;HTTP-only&lt;/strong&gt; cookies are more secure as they are &lt;strong&gt;accessible only to the server&lt;/strong&gt;. The browser is designed to prevent any JavaScript from reading HTTP-only cookies. Moreover, cookies set by the server should be flagged as &lt;strong&gt;'Secure'&lt;/strong&gt; to ensure they are transmitted exclusively over &lt;strong&gt;HTTPS&lt;/strong&gt; connections, not &lt;strong&gt;unsecured HTTP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Another critical attribute of cookies for security is the &lt;strong&gt;'SameSite'&lt;/strong&gt; flag. Setting this flag to either &lt;strong&gt;'Strict' or 'Lax'&lt;/strong&gt;, instead of &lt;strong&gt;'None'&lt;/strong&gt;, significantly enhances security. This configuration helps prevent the browser from sending the user's cookies in response to &lt;strong&gt;Cross-Site Request Forgery (CSRF) attacks&lt;/strong&gt;. When combined with the HTTP-only and Secure flags, setting SameSite to 'Strict' provides robust protection by safeguarding against XSS, CSRF, and ensuring that all data is transmitted securely over HTTPS.&lt;/p&gt;

&lt;p&gt;For additional layers of security, the implementation of CSRF tokens can be considered.&lt;/p&gt;

&lt;p&gt;Example of setting a secure, HTTP-only and SameSite="Strict" using a fastapi server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from fastapi import FastAPI, Response, Form
from typing import Annotated
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Set up CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=[FRONTEND_URL],
    allow_credentials=True,
    allow_methods=["*"],  # Allows all methods
    allow_headers=["*"],  # Allows all headers
)

TOKEN_EXPIRE_MINUTES = 60

@app.post("/login")
async def login(email: Annotated[str, Form()], password: Annotated[str, Form()], response: Response):
    user = #Retrieve user from database using email
    if user and password == user["password"]: #Don't store password in plain text in a real application
        response.set_cookie("token", value=JWT_token, max_age=TOKEN_EXPIRE_MINUTES*60, httponly=True, secure=True, samesite="Strict", domain="your site domain")
        return True
    else:
        return False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a solid foundation in understanding the user authentication and authorization flow, valuable knowledge regardless of whether you opt for an external provider. To develop a fully in-house user authentication and authorization system, more in-depth information is required. Stay tuned for the &lt;strong&gt;second part&lt;/strong&gt; of this blog post, where I'll delve into password encryption and the intricacies of sending emails.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why we use Server Sent Events and how to implement them in FastAPI</title>
      <dc:creator>Aneesh Arora</dc:creator>
      <pubDate>Wed, 10 Jan 2024 04:33:42 +0000</pubDate>
      <link>https://forem.com/aneesh_arora/why-we-use-server-sent-events-and-how-to-implement-them-in-fastapi-28ol</link>
      <guid>https://forem.com/aneesh_arora/why-we-use-server-sent-events-and-how-to-implement-them-in-fastapi-28ol</guid>
      <description>&lt;p&gt;I, Aneesh Arora, am the cofounder and CTO of &lt;a href="https://www.afterword.tech"&gt;Afterword&lt;/a&gt; and have written this post to describe the advantages of using Server Sent Events for generative AI applications and how to implement them using FastAPI.&lt;/p&gt;

&lt;p&gt;When it came to implementing SEE using FastAPI back in July 2022 I could find only two articles on the internet and neither of them were very helpful. There might be more articles today but all of them fail to explain certain basic concepts clearly. Hence I am attempting to mitigate that gap.&lt;/p&gt;

&lt;p&gt;You can also read this on our &lt;a href="https://www.afterword.tech/blog/why-we-use-server-sent-events-fastapi"&gt;website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges in AI Model Integration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed and Content Limitation:&lt;/strong&gt;
When building an application with AI models, a key challenge is the speed of content creation and the limit on the amount of content that can be processed at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enhancing User Experience in Afterword
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dual Tasks:&lt;/strong&gt; Afterword must scrape web articles and condense their content, both time-consuming tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managing User Expectations:&lt;/strong&gt; Users expect quick results. It's vital to:

&lt;ul&gt;
&lt;li&gt;Provide partial summaries during processing.&lt;/li&gt;
&lt;li&gt;Update users on the process status.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purpose:&lt;/strong&gt; This ensures users know the application is active and not stalled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Solutions for Timely Updates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebSockets vs. Server-Sent Events (SSE):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebSockets:&lt;/strong&gt; Used in bidirectional communication (e.g.,      WhatsApp), but resource-intensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-Sent Events (SSE):&lt;/strong&gt; Offers a unidirectional communication path.

&lt;ul&gt;
&lt;li&gt;Server broadcasts updates.&lt;/li&gt;
&lt;li&gt;Client receives updates passively, different from traditional HTTP requests where client has to make a new request for every update.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Choose SSE?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Efficient Broadcasting:&lt;/strong&gt; Chosen for its efficiency in broadcasting updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Industry Validation:&lt;/strong&gt; Similar approach used by OpenAI for ChatGPT.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Early Adoption:&lt;/strong&gt; We implemented SSE in July 2022, predating ChatGPT's launch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing SSE in Afterword
&lt;/h2&gt;

&lt;p&gt;Having settled on Server-Sent Events (SSE) for Afterword, our next step was to implement it practically. For Afterword's backend, we chose FastAPI, a leading Python framework, aligning with our decision to use Python for our AI functionalities. I'll delve into Afterword's complete tech stack in a subsequent, more detailed blog post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSE implementation involves two main components:&lt;/strong&gt; the server and the client. On the server side, we needed to integrate SSE within the FastAPI framework. Regarding the client side, the implementation of SSE in JavaScript will be the same no matter which frontend framework you choose to work with unless you use HTMX (the frontend framework for javascript haters. Just kidding, it’s really cool do check it out).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FastAPI,&lt;/strong&gt; built upon the &lt;strong&gt;Starlette&lt;/strong&gt; framework, can leverage a third-party library specifically designed for Server-Sent Events (SSE), namely sse-starlette. Implementing SSE in FastAPI using this library is straightforward. Instead of returning a typical response from a FastAPI route, you use &lt;strong&gt;EventSourceResponse(generator_function())&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To understand this better, let's clarify what a &lt;strong&gt;generator function&lt;/strong&gt; is. In Python, a generator function is used to return a sequence of values over time, each time it's iterated, by employing the &lt;strong&gt;yield&lt;/strong&gt; statement. This differs from a normal function, which returns a single value using the &lt;strong&gt;return&lt;/strong&gt; statement.&lt;/p&gt;

&lt;p&gt;For instance, a &lt;strong&gt;standard function&lt;/strong&gt; is structured as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def normal_function():
    #Perform some action
    return final_value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In contrast, a &lt;strong&gt;generator function&lt;/strong&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def generator_function():
    #Perform some action
    yield first_value
    #Perform more action
    yield second_value
    #Perform even more action
    yield final_value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;strong&gt;yield&lt;/strong&gt; in the generator function produces a value that can be sent to the client as part of an SSE stream. This allows for real-time data transmission to the client, making it an ideal approach for applications that require continuous data updates, like Afterword.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standard vs SSE route
&lt;/h2&gt;

&lt;p&gt;In FastAPI, a &lt;strong&gt;standard route&lt;/strong&gt; for a simple request-response cycle looks quite straightforward. Here's an example of such a route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.get("/normal")
def hello_world():
    return {"Hello": "World"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This route responds with a simple JSON object upon a GET request.&lt;/p&gt;

&lt;p&gt;However, for a route that implements &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt;, the structure changes to accommodate the SSE pattern. This is achieved by returning an &lt;strong&gt;&lt;code&gt;EventSourceResponse&lt;/code&gt;&lt;/strong&gt; with a generator function. Here's how the SSE route would look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.get("/SSE")
def server_sent_events():
    return EventSourceResponse(generator_function())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the generator function you've described, this SSE route will send &lt;strong&gt;three separate updates&lt;/strong&gt; to the client (browser) before ceasing transmission. Each yield in your generator function corresponds to a separate update sent to the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Message format for SSE
&lt;/h2&gt;

&lt;p&gt;When using SSE, it's crucial that the messages sent to the browser adhere to the structure specified in the HTML format. Each message consists of various fields, each on a new line, defined as &lt;strong&gt;fieldname: value&lt;/strong&gt;. The key fields include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;event:&lt;/strong&gt; Specifies the event type. If this is set, it triggers a browser event for which listeners can be added using &lt;strong&gt;addEventListener()&lt;/strong&gt;. If not set, the &lt;strong&gt;onmessage&lt;/strong&gt; handler is used.&lt;/li&gt;
&lt;li&gt;    &lt;strong&gt;data:&lt;/strong&gt; Sets the event ID.&lt;/li&gt;
&lt;li&gt;    &lt;strong&gt;id:&lt;/strong&gt; Contains the actual message data.&lt;/li&gt;
&lt;li&gt;    &lt;strong&gt;retry:&lt;/strong&gt; Defines the reconnection time in milliseconds, instructing the browser how long to wait before attempting to reconnect if the connection is lost.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All other field names are &lt;strong&gt;ignored&lt;/strong&gt; by the browser.&lt;/p&gt;

&lt;p&gt;Events are &lt;strong&gt;optional&lt;/strong&gt; but they are helpful. For example by setting an event name - “end” we can tell the browser when to stop listening for more messages. Of course the same can be achieved based on certain input received through the data field as well.&lt;/p&gt;

&lt;p&gt;In your generator function, the messages are structured to include these key fields. For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def generator_function():
    yield {
        "event": "event_name",
        "id": event_id, #any unique id
        "retry": 15000, #15s
        "data": json.dumps({"message": "message text"}) #actual data being sent
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the reason we use &lt;strong&gt;json.dumps&lt;/strong&gt; is because Python data structures cannot be directly sent over a network. json.dumps serializes these structures into a string representation, which can be transmitted over the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling SSE in browser using javascript
&lt;/h2&gt;

&lt;p&gt;Receiving Server-Sent Events (SSE) in the browser using JavaScript is relatively straightforward and involves setting up an &lt;strong&gt;EventSource object&lt;/strong&gt; to listen for messages from the server. Here's a breakdown of how it works based on your provided code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Initialize the EventSource:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const evtSource = new EventSource(URL);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line creates a new &lt;strong&gt;EventSource&lt;/strong&gt; instance, connecting to the specified URL (where your FastAPI server sends SSE).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Listen for Specific Events:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;evtSource.addEventListener("event_name", function(event) {
    let data = JSON.parse(event.data);
    // Use the data
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you're adding an event listener for a specific event type, &lt;strong&gt;"event_name"&lt;/strong&gt;. When the event is received, the callback function is executed. The event data, which is a JSON string, is parsed back into a JavaScript object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Listen for a Termination Event:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;evtSource.addEventListener("end", function(event) {
    evtSource.close();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This listener waits for an &lt;strong&gt;"end"&lt;/strong&gt; event, signaling that no more messages will be sent. Upon receiving this event, it closes the &lt;strong&gt;EventSource&lt;/strong&gt; connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Error Handling:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;evtSource.onerror = function(event) {
    // Handle error
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will be called if an error occurs with the &lt;strong&gt;EventSource&lt;/strong&gt; connection. You can implement specific error handling logic here.&lt;/p&gt;

&lt;p&gt;This setup ensures that your browser client is continuously listening for messages sent from your FastAPI server via SSE. It processes each message as it arrives and responds accordingly, whether that's updating the UI, triggering further actions, or closing the connection. This method is efficient for real-time applications, as it reduces the need for repeated polling and provides a more interactive user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete working example
&lt;/h2&gt;

&lt;p&gt;Now that we know how to send and receive SSE let us look at a full example.&lt;/p&gt;

&lt;p&gt;In this we will assume that we are looping over an array and generating data and sending to the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install all dependencies:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install fastapi
pip install uvicorn[standard]
pip install sse-starlette
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Create a python file 'sse.py' and copy the below code to it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
import time
import json
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Set up CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Allows all origins
    allow_credentials=True,
    allow_methods=["*"],  # Allows all methods
    allow_headers=["*"],  # Allows all headers
)

@app.get("/SSE")
async def SSE():
    return EventSourceResponse(generator_function())

RETRY_TIMEOUT = 15000 #15s

async def generator_function():
    #We are sleeping to simulate CPU processing
    time.sleep(2) #sleep for 2 seconds
    yield {"event": "message","id": 1,"retry": RETRY_TIMEOUT,"data": json.dumps({"message":"1st SSE"})}
    time.sleep(1)
    yield {"event": "message","id": 2,"retry": RETRY_TIMEOUT,"data": json.dumps({"message":"2nd SSE"})}
    #Loop over a list and send SSE
    messages = ["data 1","data 2","data 3"]
    for message in messages:
        time.sleep(1)
        yield {"event": "message","id": 3,"retry": RETRY_TIMEOUT,"data": json.dumps({"message":message})}
    time.sleep(1)
    yield {"event": "end","id": 4,"retry": RETRY_TIMEOUT,"data": json.dumps({"message":"last SSE"})}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. CORS Middleware Configuration&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Allows all origins
    allow_credentials=True,
    allow_methods=["*"],  # Allows all methods
    allow_headers=["*"],  # Allows all headers
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets up CORS to allow requests from any origin, which is crucial for API accessibility from different domains. Since we will be making requests using javascript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Start FastAPI server:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uvicorn sse:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Setup a svelte project:&lt;/strong&gt; You can use any framework you like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create svelte@latest sse
cd sse
npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Overwrite content of sse/src/routes/+page.svelte with the code below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
let message = "";
async function SSE()
{
  const evtSource = new EventSource("http://localhost:8000/SSE");
  evtSource.addEventListener("message", function(event) {
      let data = JSON.parse(event.data);
      message = data.message;
  });
  evtSource.addEventListener("end", function(event) {
      let data = JSON.parse(event.data);
      message = data.message;
      evtSource.close();
  });
}
&amp;lt;/script&amp;gt;
&amp;lt;h1&amp;gt;SSE&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;a on:click={SSE} href="#"&amp;gt;Click&amp;lt;/a&amp;gt; to start SSE&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{message}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Start svelte server:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev -- --open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;9. Open &lt;a href="http://localhost:5173"&gt;http://localhost:5173&lt;/a&gt; and click to start SSE:&lt;/strong&gt; You should be able to see the messages in your browser&lt;/p&gt;

&lt;p&gt;Hope you found this post helpful. Do check out &lt;a href="https://www.afterword.tech"&gt;Afterword&lt;/a&gt; to take control of your reading and tackle information overload.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
