<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Ayodele Aransiola - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Ayodele Aransiola - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 14 May 2026 09:13:21 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/leomofthings/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Prevent IDOR Vulnerabilities in Next.js API Routes ]]>
                </title>
                <description>
                    <![CDATA[ Imagine this situation: A user logs in successfully to your application, but upon loading their dashboard, they see someone else’s data. Why does this happen? The authentication worked, the session is ]]>
                </description>
                <link>https://www.freecodecamp.org/news/prevent-idor-in-nextjs/</link>
                <guid isPermaLink="false">69a1f073d4053a09f3430559</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayodele Aransiola ]]>
                </dc:creator>
                <pubDate>Fri, 27 Feb 2026 19:28:51 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/b14a67ea-e78b-4ebd-996f-98da3a0a8027.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine this situation: A user logs in successfully to your application, but upon loading their dashboard, they see someone else’s data.</p>
<p>Why does this happen? The authentication worked, the session is valid, the user is authenticated, but the authorization failed.</p>
<p>This specific issue is called <strong>IDOR (Insecure Direct Object Reference)</strong>. It’s one of the most common security bugs and is categorized under <strong>Broken Object Level Authorization (BOLA)</strong> in the OWASP API Security Top 10.</p>
<p>In this tutorial, you’ll learn:</p>
<ul>
<li><p>Why IDOR happens</p>
</li>
<li><p>Why authentication alone is not enough</p>
</li>
<li><p>How object-level authorization works</p>
</li>
<li><p>How to fix IDOR properly in Next.js API routes</p>
</li>
<li><p>How to design safer APIs from the start</p>
</li>
</ul>
<h2 id="heading-table-of-content">Table of Content</h2>
<ul>
<li><p><a href="#heading-table-of-content">Table of Content</a></p>
</li>
<li><p><a href="#heading-authentication-vs-authorization">Authentication vs. Authorization</a></p>
</li>
<li><p><a href="#heading-what-is-an-idor-vulnerability">What is an IDOR Vulnerability?</a></p>
</li>
<li><p><a href="#heading-the-vulnerable-pattern-in-nextjs">The Vulnerable Pattern in Next.js</a></p>
</li>
<li><p><a href="#heading-how-to-handle-idor-in-nextjs">How to Handle IDOR in Next.js</a></p>
<ul>
<li><a href="#heading-object-level-authorization">Object-Level Authorization</a></li>
</ul>
</li>
<li><p><a href="#heading-how-to-design-safer-endpoints-apime">How to Design Safer Endpoints (/api/me)</a></p>
</li>
<li><p><a href="#heading-mental-model-for-api-design">Mental Model for API Design</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-authentication-vs-authorization">Authentication vs. Authorization</h2>
<p>Before writing further, let’s clarify something critical.</p>
<ul>
<li><p><strong>Authentication answers:</strong> Who are you?</p>
</li>
<li><p><strong>Authorization answers:</strong> What are you allowed to access?</p>
</li>
</ul>
<p>In IDOR scenarios, authentication works (the user is logged in), while authorization is missing or incomplete. That distinction is the core lesson of this article.</p>
<h2 id="heading-what-is-an-idor-vulnerability">What is an IDOR Vulnerability?</h2>
<p>An IDOR vulnerability happens when your API fetches a resource by an identifier (like a user ID), and then you do not verify that the requester owns or is allowed to access that resource.</p>
<p>Example of such a request:</p>
<pre><code class="language-plaintext">GET /api/users/123
</code></pre>
<p>The code above is an HTTP <strong>GET</strong> request to the <code>/api/users/123</code> route. The <code>GET</code> method is used to request data from the server. This indicates that the client is requesting a specific user with the ID <code>123</code> and this request returns the user data in a response (often in JSON format).</p>
<p>If your backend makes the request using a similar structure to the code snippet below without checking who is making the request, you have an IDOR vulnerability, even if the user is logged in.</p>
<pre><code class="language-tsx">db.user.findUnique({ where: { id: "123" } })
</code></pre>
<p>What the code does is to query the database for a single user record. The <code>db.user</code> part refers to the <code>user</code> model/table and <code>findUnique()</code> is a method that returns only one record based on a unique field. Inside the method, the <code>where</code> clause specifies the filter condition and <code>{ id: "123" }</code> tells the database to find the user whose unique <code>id</code> equals <code>"123"</code>. If a matching record exists, it returns that user object; otherwise, it returns <code>null</code>.</p>
<h2 id="heading-the-vulnerable-pattern-in-nextjs">The Vulnerable Pattern in Next.js</h2>
<p>Looking at this Next.js App Router API route:</p>
<pre><code class="language-tsx">// app/api/users/[id]/route.ts
import { NextResponse } from "next/server";
import { db } from "@/lib/db";

export async function GET(
  req: Request,
  { params }: { params: { id: string } }
) {
  const user = await db.user.findUnique({
    where: { id: params.id },
    select: { id: true, email: true, name: true },
  });

  return NextResponse.json({ user });
}
</code></pre>
<p>Before going to the implication of this code snippet, let's understand what the code does. It defines a dynamic API route for <code>/api/users/[id]</code>. The exported <code>GET</code> function is an async route handler that runs when a GET request is made to this endpoint. It receives the request object and a <code>params</code> object, where <code>params.id</code> contains the dynamic <code>[id]</code> in the URL segment. The <code>db.user.findUnique()</code> method queries the database for a user whose <code>id</code> matches <code>params.id</code>, and the <code>select</code> option limits the returned fields to <code>id</code>, <code>email</code>, and <code>name</code>. Finally, <code>NextResponse.json()</code> sends the retrieved user data back to the client as a JSON response.</p>
<p>Now, to the implication, the code is a bad approach because the route accepts a user ID from the URL, fetches that user directly from the database, and returns the result. There is no session validation, no ownership check, and no role check.</p>
<p>If a logged-in user changes the <code>id</code> in the URL, they may access other users’ data. This is simply IDOR.</p>
<h2 id="heading-how-to-handle-idor-in-nextjs">How to Handle IDOR in Next.js</h2>
<p>The first element of defense is verifying identity. We’ll use <code>getServerSession</code> from NextAuth (adjust if using another auth provider). This change ensures that you read the session from the cookies, verify it on the server side, and ensure the user has a valid ID. This prevents unauthenticated access.</p>
<pre><code class="language-tsx">// lib/auth.ts
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/authOptions";

export async function requireSession() {
  const session = await getServerSession(authOptions);

  if (!session?.user?.id) {
    return null;
  }

  return session;
}
</code></pre>
<p>The code above defines an authentication helper function called <code>requireSession</code>. The <code>getServerSession(authOptions)</code> function retrieves the current user session on the server using the provided authentication configuration. The optional chaining (<code>session?.user?.id</code>) in the <code>if</code> block that follows safely checks whether a logged-in user and their <code>id</code> exist. If no valid session or user ID is found, the function returns <code>null</code>, indicating the request is unauthenticated. Otherwise, it returns the full <code>session</code> object so it can be used in protected routes or server logic.</p>
<p>You have successfully confirmed that the user and session exist; now, update the route:</p>
<pre><code class="language-tsx">export async function GET(
  req: Request,
  { params }: { params: { id: string } }
) {
  const session = await requireSession();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const user = await db.user.findUnique({
    where: { id: params.id },
    select: { id: true, email: true, name: true },
  });

  return NextResponse.json({ user });
}
</code></pre>
<p>The fix is incomplete yet, but in the above code, you’ve prevented anonymous access. The <code>GET</code> handler calls the <code>requireSession()</code> that was created earlier to verify that the request is authenticated. If no valid session is returned, it immediately responds with a JSON error message and a <code>401 Unauthorized</code> HTTP status. If the user is authenticated, it proceeds to call <code>db.user.findUnique()</code> to fetch the user whose <code>id</code> matches <code>params.id</code>, selecting only the <code>id</code>, <code>email</code>, and <code>name</code> fields. Finally, it returns the retrieved user data as a JSON response using <code>NextResponse.json()</code>.</p>
<p>Something is still missing. Can you guess? Any authenticated user can still request any resource by changing the URL path to the request they want. How? This leads us to object-level authorization.</p>
<h3 id="heading-object-level-authorization">Object-Level Authorization</h3>
<p>An object-level authorization ensures that a user can only access their own data (unless explicitly permitted).</p>
<p>The improvement to the code would be to add an ownership check. The adjustment ensures the API request checks if the requester is authenticated and owns the requested object. If either fails, access is denied.</p>
<pre><code class="language-tsx">export async function GET(
  req: Request,
  { params }: { params: { id: string } }
) {
  const session = await requireSession();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  if (session.user.id !== params.id) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  }

  const user = await db.user.findUnique({
    where: { id: params.id },
    select: { id: true, email: true, name: true },
  });

  return NextResponse.json({ user });
}
</code></pre>
<p>Let's take a look at what happened in the code, the <code>GET</code> handler first authenticates the request using <code>requireSession()</code>, returning a <code>401</code> response if no valid session exists. It then performs an authorization check by comparing <code>session.user.id</code> with <code>params.id</code>. If they do not match, it returns a <code>403 Forbidden</code> response, preventing users from accessing other users’ data. If both checks pass, it queries the database using <code>db.user.findUnique()</code> to retrieve the specified user and limits the result to selected fields. Finally, it sends the user data back as a JSON response. With this, you’ve enforced an <strong>object-level authorization</strong>.</p>
<h2 id="heading-how-to-design-safer-endpoints-apime">How to Design Safer Endpoints (<code>/api/me</code>)</h2>
<p>The safest approach in designing your endpoint is to eliminate the risk entirely. Instead of allowing users to specify IDs (<code>/api/users/:id</code>), use <code>/api/me</code>, because the server already knows the user’s identity from the session.</p>
<pre><code class="language-tsx">// app/api/me/route.ts
export async function GET() {
  const session = await requireSession();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const user = await db.user.findUnique({
    where: { id: session.user.id },
    select: { id: true, email: true, name: true },
  });

  return NextResponse.json({ user });
}
</code></pre>
<p>This approach makes sure that your API only returns data for the currently authenticated user. It first calls <code>requireSession()</code> to ensure the request is authenticated, returning a <code>401</code> response if no session exists. Instead of using a URL parameter, it reads the user’s ID directly from <code>session.user.id</code>, ensuring the user can only access their own data. It then calls <code>db.user.findUnique()</code> to retrieve that user from the database, selecting only specific fields, and returns the result as a JSON response.</p>
<p>You can be confident with this approach because the client cannot manipulate user IDs. The server gets the user identity from a trusted source, and the attack surface is reduced. This is called <code>secure-by-design</code> <strong>API model</strong>.</p>
<p>Now, you should clearly understand that authentication does not imply authorization. Hence,</p>
<ul>
<li><p>IDOR occurs when object ownership is not verified</p>
</li>
<li><p>Every API route that accepts an ID must validate access</p>
</li>
<li><p>Safer API design reduces vulnerability surface</p>
</li>
<li><p>Authorization must always run on the server</p>
</li>
</ul>
<h2 id="heading-mental-model-for-api-design">Mental Model for API Design</h2>
<p>When writing any API route, answer these questions:</p>
<ol>
<li><p>Who is making this request?</p>
</li>
<li><p>What object are they requesting?</p>
</li>
<li><p>Does policy allow them to access it?</p>
</li>
</ol>
<p>If you cannot clearly answer all three, your route may be vulnerable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>IDOR vulnerabilities happen when APIs trust user-supplied identifiers without verifying ownership or permission.</p>
<p>To prevent them in Next.js, authenticate every private route, enforce object-level authorization, centralize authorization logic, and write tests for forbidden access.</p>
<p>Security is not about adding logins, it’s about enforcing security policy on every object access.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create Role-Based Access Control (RBAC) with Custom Claims Using Firebase Rules ]]>
                </title>
                <description>
                    <![CDATA[ When you’re building an application, not all users should have the same level of access. For example, an admin might be able to update or delete some data (logs excluded), while a regular user should only be able to read it. This is where Role-Based ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/firebase-rbac-custom-claims-rules/</link>
                <guid isPermaLink="false">68effb6136e271937e23c188</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ rbac ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayodele Aransiola ]]>
                </dc:creator>
                <pubDate>Wed, 15 Oct 2025 19:52:01 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760557889448/ac51a7a3-cdd8-46c9-964d-a7e281e1affc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re building an application, not all users should have the same level of access. For example, an admin might be able to update or delete some data (logs excluded), while a regular user should only be able to read it. This is where <strong>Role-Based Access Control (RBAC)</strong> comes in.</p>
<p><a target="_blank" href="https://firebase.google.com/">Firebase</a> makes this possible with <a target="_blank" href="https://firebase.google.com/docs/auth/admin/custom-claims">custom claims</a> and security rules. In this article, you’ll learn how to:</p>
<ul>
<li><p>Add custom claims to users with the Firebase Admin SDK.</p>
</li>
<li><p>Use Firebase Security Rules to enforce RBAC.</p>
</li>
<li><p>Test your rules with different roles.</p>
</li>
</ul>
<p>By the end, you’ll have a working setup where roles like <code>admin</code> and <code>user</code> are enforced directly in Firestore.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-understand-firebase-custom-claims">Step 1: Understand Firebase Custom Claims</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-assign-a-role-with-the-firebase-admin-sdk">Step 2: Assign a Role with the Firebase Admin SDK</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-write-and-enforce-firestore-security-rules">Step 3: Write and Enforce Firestore Security Rules</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-build-the-frontend-with-nextjs-and-firebase">Step 4: Build the Frontend with Next.js and Firebase</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-test-the-rbac-workflow">Step 5: Test the RBAC Workflow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along, you should:</p>
<ul>
<li><p>Have a Firebase project set up with Authentication and Firestore enabled.</p>
</li>
<li><p>Be comfortable with JavaScript/Node.js.</p>
</li>
<li><p>Have the Firebase SDK and Admin SDK installed.</p>
</li>
</ul>
<p>If you’re new to Firebase, check out the <a target="_blank" href="https://firebase.google.com/docs/web/setup">official setup guide</a> before continuing.</p>
<h2 id="heading-step-1-understand-firebase-custom-claims">Step 1: Understand Firebase Custom Claims</h2>
<p>Firebase custom claims allow you to attach extra information (like a role) to a user’s authentication token. You set this information <strong>server-side</strong> using the Admin SDK. They are included in the user’s <code>request.auth.token</code>, and you can’t set them directly from the client (for security reasons).</p>
<p>Here’s an example: a user’s ID token might look like this after a claim is added:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"user_id"</span>: <span class="hljs-string">"abc123"</span>,
  <span class="hljs-attr">"email"</span>: <span class="hljs-string">"jane@example.com"</span>,
  <span class="hljs-attr">"role"</span>: <span class="hljs-string">"admin"</span>
}
</code></pre>
<p>In this example, the <code>role</code> field determines access privileges in your application. Firebase automatically includes this claim in the user’s ID token, so it can be securely validated both on the server and in Firestore rules.</p>
<h2 id="heading-step-2-assign-a-role-with-the-firebase-admin-sdk">Step 2: Assign a Role with the Firebase Admin SDK</h2>
<p>The Firebase Admin SDK allows you to manage users and assign roles securely from your backend (or through a script).</p>
<p>First, install the Admin SDK in a Node environment (not in your frontend app):</p>
<pre><code class="lang-bash">npm install firebase-admin
</code></pre>
<p>Then initialize it with your Firebase service account credentials:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> admin = <span class="hljs-built_in">require</span>(<span class="hljs-string">"firebase-admin"</span>);
<span class="hljs-keyword">const</span> serviceAccount = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./service-account.json"</span>);

admin.initializeApp({
  <span class="hljs-attr">credential</span>: admin.credential.cert(serviceAccount),
});
</code></pre>
<p>To get your service-account.json file, navigate to your firebase settings &gt; project settings &gt; service account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760235082674/8f542b00-2246-4585-b267-f8cb663797ea.png" alt="an image showing the service account interface on firebase console" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Click on Generate private key, and it will automatically download the JSON file. You can rename the file or use it as it is.</p>
<p>You can now define a simple function to set a user’s role:</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUserRole</span>(<span class="hljs-params">uid, role</span>) </span>{
  <span class="hljs-keyword">await</span> admin.auth().setCustomUserClaims(uid, { role });
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Role <span class="hljs-subst">${role}</span> assigned to user <span class="hljs-subst">${uid}</span>`</span>);
}
</code></pre>
<p>The <code>role</code> parameter can be anything you define, for example:</p>
<ul>
<li><p><code>"admin"</code>: Full read/write access.</p>
</li>
<li><p><code>"editor"</code>: Can create or modify limited content.</p>
</li>
<li><p><code>"user"</code>: Read-only access.</p>
</li>
</ul>
<p>The role you assign to a user depends on your app’s needs. In most applications, you’ll start simple, perhaps just <code>admin</code> and <code>user</code> and expand over time.</p>
<h3 id="heading-usage-example">Usage example:</h3>
<p>Once you’ve defined the function, call it with a user’s UID:</p>
<pre><code class="lang-js">setUserRole(<span class="hljs-string">"USER_UID_HERE"</span>, <span class="hljs-string">"admin"</span>);
</code></pre>
<p>This securely attaches a custom claim to the user.</p>
<p><strong>Note:</strong> The user must log out and log back in (or refresh their token) for the new claim to take effect.</p>
<h2 id="heading-step-3-write-firestore-security-rules-for-rbac">Step 3: Write Firestore Security Rules for RBAC</h2>
<p>Firestore <code>Security Rules</code> control how your data can be read or written. They are executed <strong>before</strong> any client request reaches your database, ensuring that your security logic isn’t bypassed.</p>
<p>Open your Firestore Rules (<code>firestore.rules</code>) and define role-based access like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760234391658/9e9cf217-6291-4189-a135-8310c8905587.png" alt="image showing the firebase rules section" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<pre><code class="lang-js">rules_version = <span class="hljs-string">'2'</span>;
service cloud.firestore {
  match /databases/{database}/documents {

    match /posts/{postId} {
      <span class="hljs-comment">// Anyone logged in can read</span>
      allow read: <span class="hljs-keyword">if</span> request.auth != <span class="hljs-literal">null</span>;

      <span class="hljs-comment">// Only admins can write</span>
      allow write: <span class="hljs-keyword">if</span> request.auth.token.role == <span class="hljs-string">"admin"</span>;
    }
  }
}
</code></pre>
<p>Here’s what’s happening:</p>
<ul>
<li><p><code>request.auth != null</code>: ensures the user is logged in.</p>
</li>
<li><p><code>request.auth.token.role == "admin"</code>: Grants write access only to users with the admin role.</p>
</li>
</ul>
<p>You can expand this for multiple roles:</p>
<pre><code class="lang-js">allow write: <span class="hljs-keyword">if</span> request.auth.token.role <span class="hljs-keyword">in</span> [<span class="hljs-string">"admin"</span>, <span class="hljs-string">"editor"</span>];
</code></pre>
<h2 id="heading-quick-reference">Quick Reference</h2>
<p>Keep these points in mind when managing Firebase RBAC:</p>
<ul>
<li><p><strong>Keep your roles simple</strong> (for example, <code>admin</code>, <code>editor</code>, <code>user</code>). Don’t overcomplicate.</p>
</li>
<li><p><strong>Don’t store roles in Firestore documents</strong>. Enforce via custom claims instead.</p>
</li>
<li><p><strong>Always test rules</strong> locally before deploying.</p>
</li>
<li><p>Remember that users must <strong>refresh their tokens</strong> after claims are updated.</p>
</li>
</ul>
<h2 id="heading-step-4-build-the-frontend-with-nextjs-and-firebase">Step 4: Build the Frontend with Next.js and Firebase</h2>
<p>Let’s bring this to life with a <a target="_blank" href="https://github.com/CodeLeom/firebase-rbac">working demo</a> using Next.js and Firebase.</p>
<pre><code class="lang-bash">firebase-rbac/
├── firebase-admin-scripts/       <span class="hljs-comment"># Server-side scripts for setting user roles</span>
│   ├── assignRole.js             <span class="hljs-comment"># Uses Firebase Admin SDK to assign custom claims</span>
│   ├── .env                      <span class="hljs-comment"># Contains service account path and test UID</span>
│   └── fir-rbac-...json          <span class="hljs-comment"># Firebase Admin SDK service account json file</span>
│
├── src/
│   ├── app/
│   │   ├── page.js               <span class="hljs-comment"># Main Next.js page for login + post display</span>
│   │   ├── layout.js             <span class="hljs-comment"># Global layout</span>
│   │   └── globals.css           <span class="hljs-comment"># Tailwind global styles</span>
│   └── lib/
│       └── firebase.js           <span class="hljs-comment"># Firebase client initialization</span>
│
├── .env.local                    <span class="hljs-comment"># Firebase web config (NEXT_PUBLIC_ variables)</span>
├── package.json
└── README.md
</code></pre>
<p>In your <code>.env.local</code>, complete these variables with your Firebase project config information:</p>
<pre><code class="lang-bash">NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project-id.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project-id.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your-sender-id
NEXT_PUBLIC_FIREBASE_APP_ID=your-app-id
</code></pre>
<p>Firebase Initialization (src/lib/firebase.js):</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { initializeApp, getApps, getApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/app"</span>;
<span class="hljs-keyword">import</span> { getAuth } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
<span class="hljs-keyword">import</span> { getFirestore } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;

<span class="hljs-keyword">const</span> firebaseConfig = {
  <span class="hljs-attr">apiKey</span>: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  <span class="hljs-attr">authDomain</span>: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  <span class="hljs-attr">projectId</span>: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  <span class="hljs-attr">storageBucket</span>: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  <span class="hljs-attr">messagingSenderId</span>: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  <span class="hljs-attr">appId</span>: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

<span class="hljs-keyword">const</span> app = initializeApp(firebaseConfig);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> auth = getAuth(app);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> db = getFirestore(app);
</code></pre>
<p>Demo Component (src/app/page.js):</p>
<p>This component lets you log in, view posts, and, if you’re an admin, create new posts.</p>
<pre><code class="lang-javascript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { auth, db } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/firebase"</span>;
<span class="hljs-keyword">import</span> {
  signInWithEmailAndPassword,
  onAuthStateChanged,
  signOut,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
<span class="hljs-keyword">import</span> { collection, getDocs, addDoc } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Page</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [posts, setPosts] = useState([]);
  <span class="hljs-keyword">const</span> [newPost, setNewPost] = useState(<span class="hljs-string">""</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> unsubscribe = onAuthStateChanged(auth, <span class="hljs-keyword">async</span> (u) =&gt; {
      setUser(u);
      <span class="hljs-keyword">if</span> (u) <span class="hljs-keyword">await</span> loadPosts();
      <span class="hljs-keyword">else</span> setPosts([]);
    });
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> unsubscribe();
  }, []);

  <span class="hljs-keyword">const</span> loadPosts = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> snapshot = <span class="hljs-keyword">await</span> getDocs(collection(db, <span class="hljs-string">"posts"</span>));
    setPosts(snapshot.docs.map(<span class="hljs-function">(<span class="hljs-params">doc</span>) =&gt;</span> ({ <span class="hljs-attr">id</span>: doc.id, ...doc.data() })));
  };

  <span class="hljs-keyword">const</span> handleLogin = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> signInWithEmailAndPassword(auth, email, password);
      alert(<span class="hljs-string">"Logged in!"</span>);
      setEmail(<span class="hljs-string">""</span>);
      setPassword(<span class="hljs-string">""</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Login failed:"</span>, error.message);
      alert(<span class="hljs-string">"Login failed: "</span> + error.message);
    }
  };

  <span class="hljs-keyword">const</span> handleLogout = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">await</span> signOut(auth);
    setUser(<span class="hljs-literal">null</span>);
  };

  <span class="hljs-keyword">const</span> handleAddPost = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> addDoc(collection(db, <span class="hljs-string">"posts"</span>), { <span class="hljs-attr">text</span>: newPost });
      setNewPost(<span class="hljs-string">""</span>);
      <span class="hljs-keyword">await</span> loadPosts();
      alert(<span class="hljs-string">"New Post added!"</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      alert(<span class="hljs-string">"Opps!! Only admins can add posts."</span>);
      <span class="hljs-built_in">console</span>.error(e.message);
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"min-h-screen flex flex-col items-center justify-center bg-gray-900 text-gray-100 px-4"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full max-w-md bg-gray-800 rounded-2xl shadow-lg p-8 space-y-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-2xl font-bold text-center text-indigo-400"</span>&gt;</span>
          Firebase RBAC Demo (Next.js)
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

        {/* Login Form */}
        {!user ? (
          <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleLogin}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"space-y-4"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"block text-gray-300 text-sm mb-1"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
                placeholder="you@example.com"
                required
                className="w-full px-3 py-2 rounded-md bg-gray-700 border border-gray-600 text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-400"
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"block text-gray-300 text-sm mb-1"</span>&gt;</span>
                Password
              <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
                placeholder="••••••••"
                required
                className="w-full px-3 py-2 rounded-md bg-gray-700 border border-gray-600 text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-400"
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full px-6 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 transition font-medium text-white"</span>
            &gt;</span>
              Login
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
        ) : (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"space-y-6"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col items-center"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-300 mb-2"</span>&gt;</span>
                Logged in as{" "}
                <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-semibold text-indigo-400"</span>&gt;</span>
                  {user.email}
                <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleLogout}</span>
                <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-red-400 hover:text-red-300 underline"</span>
              &gt;</span>
                Logout
              <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"border-t border-gray-700 pt-4"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-lg font-semibold text-indigo-300 mb-3"</span>&gt;</span>
                Posts
              <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

              {posts.length &gt; 0 ? (
                <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"space-y-2"</span>&gt;</span>
                  {posts.map((p) =&gt; (
                    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>
                      <span class="hljs-attr">key</span>=<span class="hljs-string">{p.id}</span>
                      <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gray-700 rounded-md px-3 py-2 text-gray-200"</span>
                    &gt;</span>
                      {p.text}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                  ))}
                <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
              ) : (
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-400 italic"</span>&gt;</span>No posts yet.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              )}

              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-4 flex items-center gap-2"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                  <span class="hljs-attr">value</span>=<span class="hljs-string">{newPost}</span>
                  <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setNewPost(e.target.value)}
                  placeholder="New post"
                  className="flex-1 px-3 py-2 rounded-md bg-gray-700 border border-gray-600 text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-400"
                /&gt;
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                  <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleAddPost}</span>
                  <span class="hljs-attr">className</span>=<span class="hljs-string">"px-4 py-2 rounded-md bg-indigo-600 hover:bg-indigo-500 transition font-medium text-white"</span>
                &gt;</span>
                  Add
                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        )}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">footer</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-8 text-gray-500 text-sm"</span>&gt;</span>
        Built with Next.js + Firebase | <span class="hljs-symbol">&amp;copy;</span> FreeCodeCamp 2025
      <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-step-5-test-the-rbac-workflow">Step 5: Test the RBAC Workflow</h2>
<p>Now that everything is set up, it’s time to test the entire Role-Based Access Control flow to ensure your rules and roles are working correctly.</p>
<h3 id="heading-enable-authentication">Enable Authentication</h3>
<p>Head over to your Firebase Console, select your project, and navigate to Authentication then Sign-in method. Select Add New Provider. Then enable Email/Password authentication. This will let you create and sign in with test users directly from your app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760441502551/7ce05b9b-51f2-4704-83ed-1b62bf22ef2c.png" alt="an image of authentication section on firebase" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-configure-firestore-rules">Configure Firestore Rules</h3>
<p>Next, you’ll need to update the Firestore rules. Navigate to Firestore Database, located in the build drop-down. Once you’re there, click on Rules where you will be able to update the rules.</p>
<p>Replace the default rules with the RBAC rules you defined earlier. These rules ensure that only authenticated users can read data, and only admins can create or modify posts.</p>
<p>Then publish the updated version and you are good to go.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760441655143/9a34a908-0692-4f84-92c4-7526aafdbd51.png" alt="9a34a908-0692-4f84-92c4-7526aafdbd51" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-assign-a-role-to-a-user">Assign a Role to a User</h3>
<p>To test admin permissions, assign an admin role to one of your test users. Open your terminal, change into the firebase-admin-scripts directory, and run:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> firebase-admin-scripts
node assignRole.js
</code></pre>
<p>This executes the Admin SDK script that adds a custom claim to your test user. Once the role is set, you’ll get a message confirming that the <code>admin</code> role has been assigned to the specified user ID.</p>
<p>If the user is logged in already, the user must log out and log back in for the new role to take effect.</p>
<h3 id="heading-run-the-app">Run the App</h3>
<p>Now you can start your Next.js development server:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Visit <a target="_blank" href="http://localhost:3000">http://localhost:3000</a> in your browser. You should find the Firebase RBAC demo app.</p>
<h3 id="heading-verify-role-based-access">Verify Role-Based Access</h3>
<p>Try logging in as the user who was assigned the <strong>admin</strong> role. Once logged in, you should be able to create new posts successfully. Next, log in as a regular user. You’ll notice that you can view existing posts, but any attempt to add a new post will fail with a “Permission denied” alert.</p>
<p>If you see these behaviors, then your RBAC system is working as intended!</p>
<p>By enforcing permissions at the Firestore layer, you ensure that security is handled centrally and can’t be bypassed by manipulating the client-side code. This approach keeps your app secure and scalable, even as your roles and data grow more complex.</p>
<p>Next steps:</p>
<ul>
<li><p>Add more roles (like editor, and more as you wish).</p>
</li>
<li><p>Combine RBAC with document-level validation for fine-grained control.</p>
</li>
<li><p>Explore Firebase’s <a target="_blank" href="https://firebase.google.com/docs/rules/">security rules</a>.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You just learned a simple but important <strong>role-based access control (RBAC)</strong> functionality in Firebase. In this guide, we covered custom claims and how to set roles using the Admin SDK. You also learned how to enforce those roles in Firestore security rules.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Run Python GUI Apps in GitHub Codespaces with Xvfb and noVNC ]]>
                </title>
                <description>
                    <![CDATA[ GitHub Codespaces gives you a full development environment in the cloud, directly in your browser. It’s great for writing and running code, but there’s one big limitation: it doesn’t support graphical applications out of the box, especially for Pytho... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/run-python-gui-in-github-codespaces/</link>
                <guid isPermaLink="false">68c4a196984613262158d040</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ codespaces ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayodele Aransiola ]]>
                </dc:creator>
                <pubDate>Fri, 12 Sep 2025 22:41:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757716674058/86bb9af9-0977-4548-a050-36c3b9ea3e16.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>GitHub Codespaces gives you a full development environment in the cloud, directly in your browser. It’s great for writing and running code, but there’s one big limitation: it doesn’t support graphical applications out of the box, especially for Python code.</p>
<p>If you try to run a Python GUI library like Pygame, Tkinter, or PyQt inside Codespaces, you’ll get an error. That’s because Codespaces runs in a headless environment. There’s no physical display for your app to open a window on.</p>
<p>In this article, I’ll show you how to fix that. You’ll learn how to set up a virtual desktop using Xvfb and stream it into your browser using noVNC. By the end, you’ll be able to run any Python GUI application inside GitHub Codespaces.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#why-codespaces-needs-extra-setup-for-guis">Why Codespaces Needs Extra Setup for GUIs</a></p>
</li>
<li><p><a class="post-section-overview" href="#step-1-create-the-repo-and-open-codespace">Step 1: Create the Repo and Open Codespace</a></p>
</li>
<li><p><a class="post-section-overview" href="#step-2-add-the-setup-script">Step 2: Add the Setup Script</a></p>
</li>
<li><p><a class="post-section-overview" href="#step-3-start-the-gui-environment">Step 3: Start the GUI Environment</a></p>
</li>
<li><p><a class="post-section-overview" href="#step-4-open-the-novnc-desktop">Step 4: Open the noVNC Desktop</a></p>
</li>
<li><p><a class="post-section-overview" href="#step-5-run-your-python-gui-app">Step 5: Run Your Python GUI App</a></p>
</li>
<li><p><a class="post-section-overview" href="#tips">Tips</a></p>
</li>
<li><p><a class="post-section-overview" href="#conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start, you should have:</p>
<ul>
<li><p>A GitHub account and access to GitHub Codespaces.</p>
</li>
<li><p>Basic familiarity with Python.</p>
</li>
<li><p>A Python GUI app to test (we’ll use a small Pygame example).</p>
</li>
</ul>
<h2 id="heading-why-codespaces-needs-extra-setup-for-guis">Why Codespaces Needs Extra Setup for GUIs</h2>
<p>When you run GUI code like:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pygame
pygame.display.set_mode((<span class="hljs-number">800</span>, <span class="hljs-number">600</span>))
</code></pre>
<p>On your local machine, Python tells your operating system to create a window. But Codespaces runs on a server with no monitor attached. Without a display, your GUI app cannot render.</p>
<p>That’s where <a target="_blank" href="https://www.x.org/archive/X11R7.7/doc/man/man1/Xvfb.1.xhtml"><strong>Xvfb</strong></a> (X virtual framebuffer) comes in. It simulates a display in memory, so GUI programs think they’re running on a real screen. To make that screen visible in the browser, you can use <a target="_blank" href="https://novnc.com/info.html"><strong>noVNC</strong></a>, which streams the virtual display through a web client.</p>
<p>Together, Xvfb and noVNC turn Codespaces into a cloud-based desktop for GUI apps.</p>
<h2 id="heading-step-1-create-the-repo-and-open-codespace">Step 1: Create the Repo and Open Codespace</h2>
<p>First, create a GitHub repository for your project (or a demo repo) and open it in Codespaces:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757336978616/4a41c394-0d85-48de-b7d6-14a0a3dae56d.jpeg" alt="Screenshot of a new repository" class="image--center mx-auto" width="1080" height="1080" loading="lazy"></p>
<h2 id="heading-step-2-add-the-setup-script">Step 2: Add the Setup Script</h2>
<p>Create a file called <code>start-gui.sh</code> in the root of your project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757337705472/b1b75b17-57b9-437c-a79e-18f6b1f55cae.png" alt="a screenshot of GitHub codespace with a bash file created" class="image--center mx-auto" width="1080" height="1080" loading="lazy"></p>
<p>Paste the following code into the <code>start-gui.sh</code> file:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>
<span class="hljs-built_in">set</span> -e

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Installing dependencies..."</span>
sudo apt-get update -y
sudo apt-get install -y xvfb x11vnc fluxbox websockify novnc

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting virtual display..."</span>
Xvfb :1 -screen 0 1024x768x24 &amp;
<span class="hljs-built_in">export</span> DISPLAY=:1
fluxbox &amp;

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting VNC server..."</span>
x11vnc -display :1 -nopw -forever -shared -rfbport 5900 &amp;

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting noVNC on port 6080..."</span>
websockify --web=/usr/share/novnc 6080 localhost:5900 &amp;

<span class="hljs-built_in">echo</span> <span class="hljs-string">""</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"GUI environment is ready!"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Go to the Ports tab, set port 6080 to Public, and open the link."</span>
</code></pre>
<p>Let’s explain this script so you can understand what it does:</p>
<h3 id="heading-set-e"><code>set -e</code></h3>
<ul>
<li><p>This tells the shell to exit immediately if any command fails.</p>
</li>
<li><p>Without it, the script would keep running even if something goes wrong (like a failed install).</p>
</li>
</ul>
<h3 id="heading-installing-dependencies">Installing Dependencies</h3>
<p><code>sudo apt-get update -y</code>: updates your package list.</p>
<p><code>sudo apt-get install -y</code> ⁣installs the packages we listed (xvfb, x11vnc, fluxbox, websockify, and novnc)</p>
<ul>
<li><ul>
<li><p><strong>xvfb</strong>: creates a “dummy” display (virtual screen in memory).</p>
<ul>
<li><p><strong>x11vnc</strong>: shares that dummy display via the VNC protocol.</p>
</li>
<li><p><strong>Fluxbox:</strong> a lightweight window manager, so the desktop has a GUI environment.</p>
</li>
<li><p><strong>websockify</strong>: converts VNC traffic into WebSockets so it can run in a browser.</p>
</li>
<li><p><strong>novnc</strong>: provides a browser client to connect to the desktop.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="heading-virtual-display">Virtual Display</h3>
<ul>
<li><p><code>Xvfb :1 -screen 0 1024x768x24 &amp;</code>: Starts the virtual framebuffer on display <code>:1</code> with resolution <code>1024x768</code> and 24-bit color.</p>
</li>
<li><p><code>export DISPLAY=:1</code>: tells apps (like Python GUIs) to draw on this virtual screen instead of looking for a real display unit.</p>
</li>
<li><p><code>fluxbox &amp;</code>: launches the window manager so GUI apps have a desktop to sit in.</p>
</li>
</ul>
<h3 id="heading-vnc-server">VNC Server</h3>
<ul>
<li><p><code>x11vnc -display :1</code>: connects you to the dummy display (<code>:1</code>).</p>
</li>
<li><p><code>-nopw</code>: ensures that no password is required.</p>
</li>
<li><p><code>-forever</code>: this keeps the VNC running even if clients disconnect.</p>
</li>
<li><p><code>-shared</code>: allows multiple clients.</p>
</li>
<li><p><code>-rfbport 5900</code>: exposes the internal server on VNC’s standard port.</p>
</li>
</ul>
<h3 id="heading-novnc-server">noVNC Server</h3>
<ul>
<li><p><code>websockify</code> acts as a bridge that converts WebSocket traffic to VNC protocol (on port 5900).</p>
</li>
<li><p><code>--web=/usr/share/novnc</code>: serves the noVNC web client files.</p>
</li>
<li><p><code>6080</code>: the port where you’ll connect in your browser (this is publicly accessible).</p>
</li>
<li><p><a target="_blank" href="http://localhost:5900"><code>localhost:5900</code></a>:forwards traffic to the VNC server that was started earlier.</p>
</li>
</ul>
<p>You should only expose <strong>port 6080</strong> (noVNC) as <strong>Public</strong> and keep <strong>5900</strong> (raw VNC) private because:</p>
<ul>
<li><p><strong>Prt 5900 (VNC)</strong> uses the raw VNC protocol, which is <strong>not encrypted</strong> and doesn’t require a password in this setup. If exposed, anyone could connect directly and control your Codespace desktop.</p>
</li>
<li><p><strong>Prt 6080 (noVNC)</strong> runs over <strong>WebSockets + HTTPS</strong>, so traffic is encrypted and secured through GitHub Codespaces’ connection. It also only serves the noVNC web client, not the raw VNC protocol.</p>
</li>
</ul>
<blockquote>
<p><strong>5900 = unsafe to expose</strong>, <strong>6080 = browser-safe way to view the GUI</strong>.</p>
</blockquote>
<p>The next step is for you to make the bash file executable by running the code below in the terminal:</p>
<pre><code class="lang-bash">chmod +x start-gui.sh
</code></pre>
<h2 id="heading-step-3-start-the-gui-environment">Step 3: Start the GUI Environment</h2>
<p>Run the script:</p>
<pre><code class="lang-bash">./start-gui.sh
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757339165120/f99b3381-d038-4eb6-944d-3fabad058c95.png" alt="the terminal while running the bash script" class="image--center mx-auto" width="1080" height="1080" loading="lazy"></p>
<p>This will:</p>
<ol>
<li><p>Install all dependencies (Xvfb, fluxbox, x11vnc, novnc).</p>
</li>
<li><p>Start a virtual display (<code>DISPLAY=:1</code>).</p>
</li>
<li><p>Launch a lightweight window manager (fluxbox).</p>
</li>
<li><p>Stream the desktop to your browser via noVNC on port <code>6080</code>.</p>
</li>
</ol>
<h2 id="heading-step-4-open-the-novnc-desktop">Step 4: Open the noVNC Desktop</h2>
<ol>
<li><p>In Codespaces, open the Ports tab.</p>
</li>
<li><p>Find port 6080 and change its visibility to public. (right-click on the private word)</p>
</li>
<li><p>Open the URL in a new browser tab.</p>
</li>
<li><p>Click <code>vnc.html</code> or <code>vnc_auto.html</code> if prompted.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757339315843/e3fcfd7b-d946-4c46-b4a9-09b905574a80.png" alt="screenshot of the port tab on codespace" class="image--center mx-auto" width="1394" height="331" loading="lazy"></p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757339400603/d8e0e412-7669-4c41-b1e4-5f13d8125de1.png" alt="open the port 6080 forwared address in a new tab, and you'd get this screen" class="image--center mx-auto" width="413" height="256" loading="lazy"></p>
<p>You should now see a lightweight Linux desktop running inside your browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757339486345/f5831d0e-d66e-4549-902a-13e3c8bd8bf0.png" alt="a light weight linux desktop running on your browser" class="image--center mx-auto" width="1443" height="1077" loading="lazy"></p>
<h2 id="heading-step-5-run-your-python-gui-app">Step 5: Run Your Python GUI App</h2>
<p>In a new Codespaces terminal, run:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> DISPLAY=:<span class="hljs-number">1</span>
python3 your_script.py
</code></pre>
<p>Your Python GUI app should appear inside the noVNC desktop 🎉.</p>
<p>For example, here’s a simple Pygame script <code>test.py</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pygame
<span class="hljs-keyword">from</span> pygame <span class="hljs-keyword">import</span> display, font, event
<span class="hljs-keyword">from</span> pygame.locals <span class="hljs-keyword">import</span> *

<span class="hljs-comment"># Setup display</span>
pygame.init()
screen = display.set_mode()
display.set_caption(<span class="hljs-string">"Capstone 2"</span>)
myFont = font.SysFont(<span class="hljs-string">'arial'</span>, <span class="hljs-number">12</span>)  <span class="hljs-comment"># Choose a font to use in game</span>

<span class="hljs-comment"># Directions displayed throughout game</span>
directions = <span class="hljs-string">"Please press the 'Y' key for yes and the 'N' key for no."</span>

<span class="hljs-comment"># Counts how many questions have been asked</span>
currentQuestion = <span class="hljs-number">0</span>


<span class="hljs-comment"># Determines which question to ask</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">story</span>(<span class="hljs-params">answer, count</span>):</span>
    screen.fill(<span class="hljs-string">"white"</span>)
    <span class="hljs-keyword">if</span> count == <span class="hljs-number">0</span>:
        question1(answer)
    <span class="hljs-keyword">elif</span> count == <span class="hljs-number">1</span>:
        question2(answer)
    <span class="hljs-keyword">elif</span> count == <span class="hljs-number">2</span>:
        question3(answer)
    <span class="hljs-keyword">elif</span> count == <span class="hljs-number">3</span>:
        end(answer)


<span class="hljs-comment"># Displays the first part of the story</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">intro</span>():</span>
    <span class="hljs-comment"># Break up the string into multiple variables because there isn't text wrapping in Pygame</span>
    intro1 = <span class="hljs-string">"Once upon a time lived a brave hero named Anya."</span>
    intro2 = <span class="hljs-string">"She lived a simple life in a small village, making biscuits for the village people."</span>
    intro3 = <span class="hljs-string">"One day, late at night, she hears a loud noise outside the village."</span>
    q1 = <span class="hljs-string">"Should she go outside to investigate? Yes or no?"</span>

    screen.fill(<span class="hljs-string">"white"</span>)
    textSurface = myFont.render(intro1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
    textSurface = myFont.render(intro2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
    textSurface = myFont.render(intro3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
    textSurface = myFont.render(q1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
    textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))


<span class="hljs-comment"># First question</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">question1</span>(<span class="hljs-params">answer</span>):</span>
    <span class="hljs-keyword">if</span> answer == K_y:
        yes1 = <span class="hljs-string">"She ventures into the dark, prepared for danger."</span>
        yes2 = <span class="hljs-string">"Eventually, she sees an army of ogres coming toward her village!"</span>
        q2 = <span class="hljs-string">"Should she fight the ogres? Yes or no?"</span>

        textSurface = myFont.render(yes1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
        textSurface = myFont.render(yes2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
        textSurface = myFont.render(q2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
        textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))

    <span class="hljs-keyword">elif</span> answer == K_n:
        no1 = <span class="hljs-string">"She chooses the safety of her home and stays inside."</span>
        no2 = <span class="hljs-string">"However, the sounds do not go away."</span>
        no3 = <span class="hljs-string">"She can tell something is very wrong..."</span>
        no4 = <span class="hljs-string">"Eventually, she sees an army of ogres coming toward her village!"</span>
        q2 = <span class="hljs-string">"Should she fight the ogres? Yes or no?"</span>

        textSurface = myFont.render(no1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
        textSurface = myFont.render(no2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
        textSurface = myFont.render(no3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
        textSurface = myFont.render(no4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
        textSurface = myFont.render(q2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
        textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))


<span class="hljs-comment"># Second question</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">question2</span>(<span class="hljs-params">answer</span>):</span>
    <span class="hljs-keyword">if</span> answer == K_y:
        yes1 = <span class="hljs-string">"She bravely confronts the ogres, hoping to protect her village from harm."</span>
        textSurface = myFont.render(yes1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))

    <span class="hljs-keyword">elif</span> answer == K_n:
        no1 = <span class="hljs-string">"The ogres raid the village but Anya manages to escape with her life."</span>
        textSurface = myFont.render(no1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))

    story2 = <span class="hljs-string">"The ogres decide to leave but she knows they will be back."</span>
    story3 = <span class="hljs-string">"Anya decides to talk with a village elder about what she should do."</span>
    story4 = <span class="hljs-string">"The elder says there is a powerful sword hidden in the Ancient Forest."</span>
    q3 = <span class="hljs-string">"Should Anya risk her life to retrieve it? Yes or no?"</span>

    textSurface = myFont.render(story2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
    textSurface = myFont.render(story3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
    textSurface = myFont.render(story4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
    textSurface = myFont.render(q3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
    textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
    screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))


<span class="hljs-comment"># Third question</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">question3</span>(<span class="hljs-params">answer</span>):</span>
    <span class="hljs-keyword">if</span> answer == K_y:
        yes1 = <span class="hljs-string">"Although Anya almost died in the Ancient Forest,"</span>
        yes2 = <span class="hljs-string">"she returns with the Sword of Legends!"</span>
        yes3 = <span class="hljs-string">"In the dead of winter, the ogres come back."</span>
        yes4 = <span class="hljs-string">"This time they are being led by their evil king."</span>
        q4 = <span class="hljs-string">"Should Anya fight the ogre king now that she has the Sword of Legends?"</span>

        textSurface = myFont.render(yes1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
        textSurface = myFont.render(yes2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
        textSurface = myFont.render(yes3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
        textSurface = myFont.render(yes4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
        textSurface = myFont.render(q4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
        textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))

    <span class="hljs-keyword">elif</span> answer == K_n:
        no1 = <span class="hljs-string">"Anya decides it's too risky to go into the forest alone."</span>
        no2 = <span class="hljs-string">"She hopes for the best with the weapons she has."</span>
        no3 = <span class="hljs-string">"In the dead of winter, the ogres come back."</span>
        no4 = <span class="hljs-string">"This time they are being led by their evil king."</span>
        q4 = <span class="hljs-string">"Should Anya fight the king even though she doesn't have the Sword of Legends?"</span>

        textSurface = myFont.render(no1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
        textSurface = myFont.render(no2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
        textSurface = myFont.render(no3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
        textSurface = myFont.render(no4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
        textSurface = myFont.render(q4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
        textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))


<span class="hljs-comment"># Ending</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">end</span>(<span class="hljs-params">answer</span>):</span>
    <span class="hljs-keyword">if</span> answer == K_y:
        yes1 = <span class="hljs-string">"Tension fills the air as she prepares to fight the king. The duel commences..."</span>
        end1 = <span class="hljs-string">"After an intense battle, Anya strikes the final blow!"</span>
        end2 = <span class="hljs-string">"The king surrenders and pleads for mercy."</span>
        end3 = <span class="hljs-string">"Anya is a true hero, who shows mercy to the king."</span>
        end4 = <span class="hljs-string">"This act of kindness warms the evil king's heart,"</span>
        end5 = <span class="hljs-string">"who promises to leave the village alone for eternity."</span>
        end6 = <span class="hljs-string">"The end!"</span>

        textSurface = myFont.render(yes1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
        textSurface = myFont.render(end1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
        textSurface = myFont.render(end2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
        textSurface = myFont.render(end3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
        textSurface = myFont.render(end4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
        textSurface = myFont.render(end5, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))
        textSurface = myFont.render(end6, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">94</span>))

    <span class="hljs-keyword">elif</span> answer == K_n:
        no1 = <span class="hljs-string">"Anya refuses to duel the king, who laughs at her cowardice."</span>
        end1 = <span class="hljs-string">"This buys some time for the villagers to escape."</span>
        end2 = <span class="hljs-string">"Sadly, the ogre king takes over Anya's village."</span>
        end3 = <span class="hljs-string">"She is just thankful that the villagers were able to get to safety."</span>
        end4 = <span class="hljs-string">"The end!"</span>

        textSurface = myFont.render(no1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
        textSurface = myFont.render(end1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
        textSurface = myFont.render(end2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
        textSurface = myFont.render(end3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
        textSurface = myFont.render(end4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))


<span class="hljs-comment"># Game loop</span>
<span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
    <span class="hljs-comment"># Checks to see if at beginning of game</span>
    <span class="hljs-keyword">if</span> currentQuestion == <span class="hljs-number">0</span>:
        intro()

    <span class="hljs-comment"># Get the most recent event</span>
    currentEvent = event.poll()

    <span class="hljs-comment"># Displays the correct question based on event that occurs</span>
    <span class="hljs-keyword">if</span> currentEvent.type == KEYDOWN:
        story(currentEvent.key, currentQuestion)
        currentQuestion = currentQuestion + <span class="hljs-number">1</span>

    <span class="hljs-comment"># add text to screen</span>
    display.update()
</code></pre>
<blockquote>
<p>code source: codecombat (developing python game)</p>
</blockquote>
<p>The code above is a simple interactive story game written with Pygame<strong>.</strong> In the first few lines, you import <strong>Pygame</strong> and its display, font, and event modules.</p>
<p><code>pygame.locals</code> brings in constants like <code>K_y</code> (Y key), <code>K_n</code> (N key), and⁣ <code>KEYDOWN</code>.</p>
<p>The preceding line then initializes Pygame and creates a window (<code>screen</code>). It also sets the window title and then loads a font for rendering the text.</p>
<p>Then you have the section for functions that power the story (<code>intro</code>, <code>question1</code>, <code>question2</code>, <code>question3</code>, and conditions based on the player’s answer).</p>
<p>In summary, the code is a choose-your-own-adventure text game with a single character, Anya. The choices of the player determine which text is shown.</p>
<p>To run this script, install <code>pygame</code>.</p>
<pre><code class="lang-bash">sudo apt-get update
sudo apt-get install -y python3-pygame
pip install pygame
</code></pre>
<p>The above code will install <code>pygame</code> into your environment, after which you can then run the script.</p>
<pre><code class="lang-bash">python3 test.py
</code></pre>
<p>When you run this inside Codespaces, the window will appear in the noVNC tab. If it doesn’t open automatically, click on <code>connect</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757340287440/77bafc3f-f3eb-402a-8ce1-9cfe6a739b3c.png" alt="screenshot of the python gui app output" class="image--center mx-auto" width="1332" height="1095" loading="lazy"></p>
<h2 id="heading-tips">Tips</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757340460701/c3c8376a-4b6f-4a05-8ed3-83c036476150.png" alt="ALSA error in codespaces" class="image--center mx-auto" width="1443" height="217" loading="lazy"></p>
<ul>
<li><p><strong>Ignore ALSA errors</strong>: Codespaces doesn’t have sound output, so audio warnings are normal.</p>
</li>
<li><p><strong>Adjust resolution</strong>: Change <code>1024x768x24</code> in the script if you want a bigger (or smaller) screen.</p>
</li>
<li><p><strong>Use with other libraries</strong>: Tkinter, PyQt, and Matplotlib interactive plots. All will work with this setup.</p>
</li>
<li><p><strong>Automate DISPLAY export</strong>: Add <code>export DISPLAY=:1</code> in your bash file if you don’t want to type it each time.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You’ve just turned GitHub Codespaces into a Python GUI environment. By using Xvfb and noVNC, you can run apps that normally require a desktop environment right inside your browser.</p>
<p>Whether you’re building games, testing interfaces, or teaching Python graphics, you can now do it all in Codespaces without leaving the cloud.</p>
<blockquote>
<p>Want to try it yourself? Clone this <a target="_blank" href="https://github.com/CodeLeom/Python-codespaces">repo</a>, run <code>./start-gui.sh</code>, and launch your first GUI app in Codespaces today.</p>
</blockquote>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
