The "database/permission-denied" error occurs when your client lacks authorization to access Firebase Realtime Database. This is commonly due to unauthenticated access, overly restrictive security rules, or authentication state sync issues. Fix it by implementing Firebase Authentication and configuring proper security rules.
This error means Firebase servers rejected your read or write operation because your security rules do not permit the access. Firebase Realtime Database enforces security rules on the server side before any data operation occurs—no client-side workaround can bypass them. The error typically indicates one of three issues: (1) the user is not authenticated when the rules require authentication, (2) the security rules explicitly deny access to the requested path, or (3) there is a timing mismatch where the client attempts database access before authentication completes.
Before any database operation, verify that the user is logged in. Use onAuthStateChanged() to wait for auth to complete:
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { getDatabase, ref, get } from "firebase/database";
const auth = getAuth();
const db = getDatabase();
onAuthStateChanged(auth, async (user) => {
if (user) {
// User is authenticated, safe to access database
const dataRef = ref(db, "users/" + user.uid);
const snapshot = await get(dataRef);
console.log(snapshot.val());
} else {
console.log("User not authenticated");
}
});Do NOT attempt database operations in the root of your app or before onAuthStateChanged fires.
Go to Firebase Console > Realtime Database > Rules tab. Start with secure defaults and grant minimum permissions:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}Replace $uid with your actual path variable. This rule allows users to only read/write their own data where the path matches their Firebase UID.
Never use this for testing (even temporarily):
{ "rules": { ".read": true, ".write": true } }This makes your database publicly writable—any attacker can delete all your data.
In Firebase Console, select the Rules tab and click "Rules Playground" (or refresh the page after editing rules). Simulate a read/write operation with a specific user ID:
1. Select "Simulate read" or "Simulate write"
2. Enter the path (e.g., /users/user123)
3. In the auth section, paste a mock user object: { "uid": "user123" }
4. Click "Run"
The playground will tell you exactly which rule lines allowed or denied the operation. If it's denied, adjust your rules before publishing.
Wrap database operations in a guard that checks auth.currentUser:
import { getAuth } from "firebase/auth";
import { getDatabase, ref, set } from "firebase/database";
const auth = getAuth();
const db = getDatabase();
async function saveUserData(data) {
if (!auth.currentUser) {
throw new Error("User must be signed in");
}
const uid = auth.currentUser.uid;
const userRef = ref(db, "users/" + uid);
await set(userRef, data);
}This ensures the user is authenticated before attempting any write, catching the problem before Firebase rejects it.
Verify you're using the correct database URL:
1. Go to Firebase Console > Realtime Database
2. Copy the Database URL (e.g., https://my-project.firebaseio.com)
3. Initialize Firebase with the correct config:
import { initializeApp } from "firebase/app";
import { getDatabase } from "firebase/database";
const firebaseConfig = {
apiKey: "...",
projectId: "my-project",
databaseURL: "https://my-project.firebaseio.com",
// ... other config
};
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);If your database is in a different region, ensure the URL matches. A region mismatch can cause "Client doesn't have permission to access the desired data" errors.
Authentication Timing: In some SDKs (especially mobile), there is a known timing issue where authentication state may not persist after the app restarts. If you're experiencing permission denied only after a restart, ensure you're using the latest SDK version (Firebase Android SDK 34.0.1+ or equivalent). Also, defer database operations until onAuthStateChanged has fired at least once.
Atomicity: Realtime Database performs atomic read operations. If you're reading from /records/ and any child node is denied by your rules, the entire read operation fails with permission denied. This is different from Firestore—you cannot partially read data. Design your rules to match your read access patterns.
Service Account Access: If you're accessing the database from a backend (Node.js, Cloud Functions), use a service account instead of client auth. Initialize Firebase Admin SDK:
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.database();
await db.ref("users/123").set({ name: "John" });Service accounts bypass client-side security rules and use database rules instead (if configured). Be very careful with this approach.
Callable Functions: INTERNAL - Unhandled exception
How to fix "Callable Functions: INTERNAL - Unhandled exception" in Firebase
auth/invalid-hash-algorithm: Hash algorithm doesn't match supported options
How to fix "auth/invalid-hash-algorithm: Hash algorithm doesn't match supported options" in Firebase
Hosting: CORS configuration not set up properly
How to fix CORS configuration in Firebase Hosting
auth/reserved-claims: Custom claims use reserved OIDC claim names
How to fix "reserved claims" error when setting custom claims in Firebase
Callable Functions: UNAUTHENTICATED - Invalid credentials
How to fix "UNAUTHENTICATED - Invalid credentials" in Firebase Callable Functions