If you are using Firebase with a React Native app, sooner or later you will probably run into something like this:
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Or:
Error: PERMISSION_DENIED: Permission denied
And the annoying part is that the error does not tell you much.
It does not say:
Your Firestore rule on this collection is blocking the request.
It just tells you that Firebase rejected it.
That can happen for several different reasons. Your Firestore rules might be doing exactly what you told them to do, your user might not actually be authenticated, App Check might be involved, or your app might be connected to a different Firebase project than you think.
So before changing random rules until the error disappears, it is better to figure out why Firebase is rejecting the request.
First, find out which Firebase service is rejecting the request
PERMISSION_DENIED is not limited to Firestore.
You can see permission-related errors with services such as:
- Cloud Firestore
- Realtime Database
- Firebase Storage
- Firebase App Check
- Firebase Authentication-related operations
So start by looking at the full error.
For example:
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
If the stack trace shows a Firestore operation such as:
firestore()
.collection("users")
.doc(userId)
.get();
then your first place to look is Firestore security rules.
If the error happens while uploading a file, Storage rules are more relevant.
This sounds obvious, but a long React Native stack trace can make it easy to focus on the wrong part.
Check your Firestore security rules
If the error comes from Firestore, check your rules first.
For example, you might have:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null;
}
}
}
This rule only allows authenticated users to access documents under:
/users/{userId}
If your app makes the request before the user has signed in, Firebase will reject it.
That can produce:
permission-denied
The important part is that Firebase is not necessarily malfunctioning here.
Your rule may simply be doing exactly what it was written to do.
Check whether the user is actually authenticated
One of the easiest mistakes is assuming that the user is signed in when the request runs.
For example:
const user = auth().currentUser;
console.log(user);
If that prints:
null
then there is no authenticated Firebase user at that moment.
A rule such as:
allow read, write: if request.auth != null;
will reject the request.
This can happen when your app starts up and immediately tries to read Firestore before Firebase Authentication has restored the user's session.
For example, you might have code that effectively does this:
const user = auth().currentUser;
firestore()
.collection("users")
.doc(user.uid)
.get();
If user is not available yet, the request can fail.
Instead, make sure your application waits for the authentication state before making requests that require authentication.
With React Native Firebase, you can listen for authentication changes:
useEffect(() => {
const unsubscribe = auth().onAuthStateChanged(user => {
if (user) {
// Safe to make requests that require authentication
}
});
return unsubscribe;
}, []);
The exact structure depends on your app, but the idea is the same:
Do not assume authentication has finished restoring just because the app has started.
Check that the rule matches the path you are actually using
This is another very common one.
Imagine your rules contain:
match /users/{userId} {
allow read, write: if request.auth != null;
}
But your app actually accesses:
/profiles/123
That rule does not apply to /profiles/123.
You need a matching rule for the path being accessed.
This becomes especially confusing when you have nested collections.
For example:
/users/123/settings/theme
is not the same path as:
/users/123
Your rules need to match the actual document path.
If you are getting PERMISSION_DENIED, look at the exact collection and document your code is trying to access.
Do not assume the rule you are looking at is the one Firebase is evaluating.
Be careful with nested Firestore documents
Suppose you have:
users/{userId}/settings/{settingId}
You might write:
match /users/{userId} {
allow read, write: if request.auth != null;
}
and expect that to cover everything underneath the user.
It does not automatically mean that every nested document is covered.
You may need a recursive wildcard depending on the structure and rules you want.
For example:
match /users/{userId}/{document=**} {
allow read, write: if request.auth != null;
}
Be careful with this kind of rule, though.
It can give access to much more data than you intended.
Do not add a broad wildcard just because it makes the error disappear.
Do not use allow read, write: if true as the final fix
You will sometimes see people recommend this while debugging:
allow read, write: if true;
Yes, it can make the permission error disappear.
It also allows anyone who can access your Firebase project from the client to read and write the matching data.
That is not a real fix.
If you temporarily use an open rule to confirm that the rules are the cause, put the secure rule back immediately afterward.
For example, if the app should only allow authenticated users:
allow read, write: if request.auth != null;
is very different from:
allow read, write: if true;
The goal is not simply to make Firebase stop complaining.
The goal is to make the rule match what your application is actually supposed to allow.
Check whether you are using the correct Firebase project
This one can waste a lot of time.
Your React Native app might be connected to a different Firebase project than the one you are looking at in the Firebase Console.
You think you are checking the rules for:
my-production-project
but the app is actually connected to:
my-development-project
Now you change rules in the console and nothing happens.
The app keeps returning:
permission-denied
because you are fixing the wrong project.
Check your Firebase configuration and make sure the project ID matches the Firebase project you are inspecting.
For Android, check the Firebase configuration used by your Android app.
For iOS, check the corresponding iOS Firebase configuration.
If you have development, staging, and production projects, this becomes especially important.
Check your Android and iOS Firebase configuration
If the problem only happens on one platform, compare the Firebase configuration between them.
For Android, make sure the correct Firebase configuration is being used.
For iOS, make sure the correct configuration is being used there as well.
A situation like this is possible:
Android → Firebase project A
iOS → Firebase project B
You can then spend hours changing Firestore rules in project A while testing an iOS build that is actually talking to project B.
If the error happens only on Android or only on iOS, platform-specific Firebase configuration should be on your checklist.
Check Firebase App Check
App Check can also be involved when Firebase rejects requests.
App Check is designed to help verify that requests are coming from your legitimate app rather than an unauthorized client.
If you recently enabled App Check or changed its configuration, check whether the failing request is related to App Check.
This becomes especially important if:
- the app worked before enabling App Check
- requests work in one environment but not another
- you are testing a development build
- you changed App Check enforcement settings
- you changed the app's Android or iOS configuration
Do not immediately disable App Check just because it is involved.
First determine whether the request is failing because of App Check configuration or because of your Firestore/Storage rules.
Those are different problems.
Development and production can behave differently
A common source of confusion is:
"It works in development, but my production build gets
PERMISSION_DENIED."
That does not necessarily mean the Firebase rules randomly changed.
Your development and production builds may be using different:
- Firebase projects
- application IDs
- bundle identifiers
- configuration files
- App Check settings
- authentication settings
- environment variables
If the problem only happens in a release build, compare the Firebase configuration between the working and failing builds.
This is usually more useful than changing your Firestore rules blindly.
Check the exact user ID being used
Suppose your rule is intended to allow users to access their own documents:
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
That means this request:
/users/123
will only work if:
request.auth.uid == "123"
If your application accidentally requests:
/users/456
while the logged-in user is actually 123, Firebase will reject it.
So log the values you are using while debugging:
console.log("Current user:", auth().currentUser?.uid);
console.log("Requested user:", userId);
If they do not match, you have found the problem.
This is much more useful than changing the security rule to allow everyone.
Check your Storage rules if the error happens during uploads
If the problem happens while uploading an image, PDF, or another file, you may actually be dealing with Firebase Storage rather than Firestore.
For example:
FirebaseError: [storage/unauthorized] User is not authorized to perform the desired action.
Storage has its own security rules.
A Firestore rule like:
allow read, write: if request.auth != null;
does not control Firebase Storage.
If the error happens during:
storage()
.ref("profile-images/avatar.jpg")
.putFile(filePath);
look at your Storage rules instead.
The service producing the error matters.
Check Realtime Database rules separately
The same idea applies to Realtime Database.
Realtime Database has its own rules structure, for example:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Changing Firestore rules will not fix a Realtime Database permission problem.
If your application uses several Firebase services, make sure you know which one is rejecting the request before changing anything.
Use the Firebase Emulator when appropriate
If you are working on complicated security rules, the Firebase Emulator Suite can make debugging much easier.
Instead of repeatedly testing rules against production data, you can test your rules locally.
This is especially useful when your rules depend on things like:
request.auth
request.auth.uid
request.resource.data
resource.data
For example, a rule might look correct at first glance but fail because the incoming document does not contain the field your rule expects.
Testing the rule with known authentication and data values makes these problems much easier to see.
Look at what changed right before the error
This is one of the most useful debugging habits for Firebase errors.
Ask:
What changed immediately before PERMISSION_DENIED appeared?
Maybe you:
- changed Firestore rules
- added authentication
- changed the user document structure
- switched Firebase projects
- enabled App Check
- changed an Android package name
- changed an iOS bundle identifier
- updated a Firebase dependency
- moved a Firestore collection
- changed the document ID
- created a new production build
The timing does not prove what caused the error, but it gives you a very good place to start.
A simple debugging order
When I get a Firebase PERMISSION_DENIED error, I usually go through these checks:
1. Identify the Firebase service
Is it:
- Firestore?
- Storage?
- Realtime Database?
- App Check?
- something else?
2. Read the exact path
What collection, document, or storage path is the app trying to access?
3. Check authentication
console.log(auth().currentUser?.uid);
Is there actually a logged-in user?
4. Check the security rule
Does the rule actually match the path being accessed?
5. Check the values used by the rule
If the rule compares user IDs, roles, or document fields, verify those values.
6. Check the Firebase project
Make sure the app and Firebase Console are using the same project.
7. Check App Check
Especially if the problem started after enabling or enforcing it.
8. Check platform-specific configuration
If only Android or iOS is affected, compare the configuration for that platform.
9. Reproduce the problem again
After making one change, test again.
This gives you a much clearer debugging trail.
Example: finding a simple rules mistake
Imagine your Firestore rule says:
match /users/{userId} {
allow read: if request.auth != null
&& request.auth.uid == userId;
}
Your app is logged in as:
abc123
but your code requests:
/users/undefined
Firebase sees:
request.auth.uid == "abc123"
and:
userId == "undefined"
The condition is false.
So Firebase returns:
permission-denied
The problem is not Firebase.
The problem is that your application generated the wrong document path.
This is why checking the exact request is so important.
Final thoughts
PERMISSION_DENIED can be frustrating because the error itself is often very short while the number of possible causes is not.
The important thing is to avoid treating it as simply:
"Firebase permissions are broken."
Start by finding out which Firebase service rejected the request, then check the exact path, authentication state, security rules, and Firebase project.
If the error appeared after a recent change, use that as a clue.
And avoid the temptation to solve everything with:
allow read, write: if true;
That only hides the real problem and can leave your data exposed.
If your error is buried inside a much larger React Native/Firebase log and it is difficult to tell whether the problem is authentication, rules, App Check, configuration, or something else, you can paste the full error into FixMyError and use it to narrow down what is actually going wrong.
Try it at https://www.fixmyerrorapp.com.
Top comments (0)