How to Configure Role-Based Access Control (RBAC) with Custom Claims Using Firebase Rules

by SkillAiNest

When you’re building an application, not all users should have the same level of access. For example, an admin may be able to update or delete some data (excluded logins), while a regular user should only be able to read it. This is the place Role Based Access Control (RBAC) comes in

Fire base With it makes it possible Customs claims and security rules. In this article, you will learn how to:

  • Add custom claims to users with Firebase Admin SDK.

  • Use Firebase Security Rules to enforce RBAC.

  • Test your rules with different characters.

In the end, you’ll have a working setup where the characters will love it admin And user Implemented directly in the FireStore.

Table of Contents

Conditions

To follow this, you should:

  • Set up a Firebase project with Authentication and FireStore.

  • Be comfortable with JavaScript/Node.js.

  • Install the Firebase SDK and Admin SDK.

If you’re new to Firebase, check out Official Setup Guide Before continuing

Step 1: Understand Firebase’s custom claims

Firebase Custom Claims allow you to attach additional information (such as a character) to a user’s authentication token. You have compiled this information Server side Using the Admin SDK They are included in the user request.auth.tokenand you can’t do them directly from the client (for security reasons).

Here’s an example: A user ID token after adding a claim might look like this:

{
  "user_id": "abc123",
  "email": "jane@example.com",
  "role": "admin"
}

In this example, role The field determines the access privileges in your application. Firebase automatically adds this claim to the user’s ID token, so it can be securely validated against server and FireStore rules.

Step 2: Assign a role with the Firebase Admin SDK

The Firebase Admin SDK allows you to manage users and assign roles securely from your backend (or via script).

First, install the Admin SDK in a Node environment (not in your frontend app):

npm install firebase-admin

Then start it with your Firebase service account credentials:

const admin = require("firebase-admin");
const serviceAccount = require("./service-account.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});

To get your service-account.json file, go to your Firebase Settings > Project Settings > Service Account.

An image showing the service account interface on the Firebase console

Click Generate Private Key, and it will automatically download the JSON file. You can rename the file or use it as is.

You can now define a simple function to set the user’s role:

async function setUserRole(uid, role) {
  await admin.auth().setCustomUserClaims(uid, { role });
  console.log(`Role ${role} assigned to user ${uid}`);
}

role The parameter can be anything you define, for example:

  • "admin": Full read/write access.

  • "editor": Can create or edit restricted content.

  • "user": Read-only access.

The role you assign to a user depends on the needs of your app. In most applications, you’ll want simple, probably fair admin And user And spread over time.

Usage example:

Once you’ve defined the function, call it with the user’s UID:

setUserRole("USER_UID_HERE", "admin");

It securely connects the customs claim with the customer.

Note: The user must log out and log back in (or refresh their token) for the new claim to take effect.

Step 3: Write FireStore Security Rules for RBAC

Fire Store Security Rules Control how your data can be read or written. They are hanged First Any client request is routed to your database, ensuring that your security logic is not bypassed.

Open your Fire Store Rules (firestore.rules) and define role-based access like this:

Displaying the Firebase Rules section

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    match /posts/{postId} {
      
      allow read: if request.auth != null;

      
      allow write: if request.auth.token.role == "admin";
    }
  }
}

Here’s what’s going on:

You can extend this for multiple roles:

allow write: if request.auth.token.role in ("admin", "editor");

Quick reference

Keep these points in mind when managing Firebase RBAC:

  • Keep your character simple (For example, adminfor , for , for , . editorfor , for , for , . user) don’t overcomplicate.

  • Do not place roles in FireStore documents. Enforce through customs claims instead.

  • Always test rules Before deploying locally.

  • Remember that users must Refresh their token After updating the claims.

Step 4: Build the frontend with Next.js and Firebase

Let’s live it up together Working demo Using next.js and Firebase.

firebase-rbac/
├── firebase-admin-scripts/       
│   ├── assignRole.js             
│   ├── .env                      
│   └── fir-rbac-...json          
│
├── src/
│   ├── app/
│   │   ├── page.js               
│   │   ├── layout.js             
│   │   └── globals.css           
│   └── lib/
│       └── firebase.js           
│
├── .env.local                    
├── package.json
└── README.md

in you .env.localcomplete these variables with information about your Firebase project configuration:

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

Firebase Initialization (src/lab/firebase.js):

import { initializeApp, getApps, getApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);

Demo component (src/app/page.js):

This component allows you to log in, view posts, and, if you’re an admin, create new posts.

"use client";

import { useState, useEffect } from "react";
import { auth, db } from "@/lib/firebase";
import {
  signInWithEmailAndPassword,
  onAuthStateChanged,
  signOut,
} from "firebase/auth";
import { collection, getDocs, addDoc } from "firebase/firestore";

export default function Page() {
  const (user, setUser) = useState(null);
  const (email, setEmail) = useState("");
  const (password, setPassword) = useState("");
  const (posts, setPosts) = useState(());
  const (newPost, setNewPost) = useState("");

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, async (u) => {
      setUser(u);
      if (u) await loadPosts();
      else setPosts(());
    });
    return () => unsubscribe();
  }, ());

  const loadPosts = async () => {
    const snapshot = await getDocs(collection(db, "posts"));
    setPosts(snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })));
  };

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      await signInWithEmailAndPassword(auth, email, password);
      alert("Logged in!");
      setEmail("");
      setPassword("");
    } catch (error) {
      console.error("Login failed:", error.message);
      alert("Login failed: " + error.message);
    }
  };

  const handleLogout = async () => {
    await signOut(auth);
    setUser(null);
  };

  const handleAddPost = async () => {
    try {
      await addDoc(collection(db, "posts"), { text: newPost });
      setNewPost("");
      await loadPosts();
      alert("New Post added!");
    } catch (e) {
      alert("Opps!! Only admins can add posts.");
      console.error(e.message);
    }
  };

  return (
    <main className="min-h-screen flex flex-col items-center justify-center bg-gray-900 text-gray-100 px-4">
      <div className="w-full max-w-md bg-gray-800 rounded-2xl shadow-lg p-8 space-y-6">
        <h1 className="text-2xl font-bold text-center text-indigo-400">
          Firebase RBAC Demo (Next.js)
        h1>

        {/* Login Form */}
        {!user ? (
          <form onSubmit={handleLogin} className="space-y-4">
            <div>
              <label className="block text-gray-300 text-sm mb-1">Emaillabel>
              <input
                type="email"
                value={email}
                onChange={(e) => 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"
              />
            div>

            <div>
              <label className="block text-gray-300 text-sm mb-1">
                Password
              label>
              <input
                type="password"
                value={password}
                onChange={(e) => 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"
              />
            div>

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

            <section className="border-t border-gray-700 pt-4">
              <h2 className="text-lg font-semibold text-indigo-300 mb-3">
                Posts
              h2>

              {posts.length > 0 ? (
                <ul className="space-y-2">
                  {posts.map((p) => (
                    <li
                      key={p.id}
                      className="bg-gray-700 rounded-md px-3 py-2 text-gray-200"
                    >
                      {p.text}
                    li>
                  ))}
                ul>
              ) : (
                <p className="text-gray-400 italic">No posts yet.p>
              )}

              <div className="mt-4 flex items-center gap-2">
                <input
                  value={newPost}
                  onChange={(e) => 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"
                />
                <button
                  onClick={handleAddPost}
                  className="px-4 py-2 rounded-md bg-indigo-600 hover:bg-indigo-500 transition font-medium text-white"
                >
                  Add
                button>
              div>
            section>
          div>
        )}
      div>

      <footer className="mt-8 text-gray-500 text-sm">
        Built with Next.js + Firebase | © FreeCodeCamp 2025
      footer>
    main>
  );
}

Step 5: Test the RBAC workflow

Now that everything is set up, it’s time to test the entire role-based access control flow to make sure your rules and roles are working correctly.

Enable authentication

Go to your Firebase console, select your project, and then go to the sign-in method to confirm. Select Add New Provider. Then enable email/password authentication. This will let you create and sign in test users directly from your app.

An image of the authentication section on Firebase

Create firestore rules

Next, you’ll need to update the Fire Store rules. Go to Firestore Database located in the Build dropdown. Once you are there, click on Rules where you will be able to update the rules.

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 edit posts.

Then publish the latest version and you’re good to go.

9A34A908-0692-4F84-92C4-7526AAFDBD51

Assign a role to a user

To test admin permissions, assign the admin role to one of your test users. Open your terminal, change to the Firebase AdminScripts directory, and run:

cd firebase-admin-scripts
node assignRole.js

This executes a script from the Admin SDK that adds custom language to your test user. Once the role is set, you will receive a message confirming that admin The role is assigned to the specified user ID.

If the user is already logged in, the user must log out and log in again to implement the new role.

Run the app

Now you can start your next one. JS Development Server:

npm run dev

See In your browser you should find Firebase RBAC Demo App.

Verify role-based access

Try logging in as the user that was assigned Admin Once the role is logged in, you should be able to successfully create new posts. 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.

If you see these behaviors, then your RBAC system is working as intended!

By enforcing permissions at the FireStore layer, you ensure that security is handled centrally and cannot be bypassed by manipulating client-side code. This approach keeps your app secure and scalable, even when your roles and data are more complex.

Next steps:

  • Add more roles (like editor, and custom).

  • Combine RBAC with document-level validation for fine-grained control.

  • Explore Firebase Security Rules.

The result

You just learned a simple but important one Role Based Access Control (RBAC) Functionality in Firebase. In this guide, we covered custom assertions and how to set roles using the Admin SDK. You also learned how to implement these roles in FireStore security rules.

You may also like

Leave a Comment

At Skillainest, we believe the future belongs to those who embrace AI, upgrade their skills, and stay ahead of the curve.

Get latest news

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

@2025 Skillainest.Designed and Developed by Pro