<?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[ rbac - 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[ rbac - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 08:56:00 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/rbac/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <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="1339" height="837" 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="534" height="214" 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="1277" height="461" 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="1277" height="586" 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>
        
    </channel>
</rss>
