Firebase Rate Limit, 401, 403, 502, 503 & Token Errors: Complete Troubleshooting Guide
Fix Firebase rate limit, 401 unauthorized, 403 forbidden, 502/503 errors, token expired, and connection refused issues with step-by-step diagnostic commands.
- Firebase 429 / RESOURCE_EXHAUSTED errors mean you've exceeded Firestore read/write quotas or Authentication sign-in limits — back off with exponential retry and review your security rules
- 401 UNAUTHENTICATED and 403 PERMISSION_DENIED errors almost always trace to expired ID tokens, misconfigured Security Rules, or a missing/wrong service-account key — refresh the token or fix the rule before retrying
- 502 / 503 errors from Firebase are transient infrastructure issues on Google's side; use the Firebase Status Dashboard to confirm, then implement jitter-based retries in your client
- Token expiry (ID tokens last 1 hour by default) causes silent 401 failures in long-lived server processes — call getIdToken(true) to force-refresh or use the Admin SDK with a service account that never expires
- Connection refused on firebase-adminsdk calls usually means a wrong project ID, a revoked service-account key, or a missing GOOGLE_APPLICATION_CREDENTIALS env var
| Method | When to Use | Time to Apply | Risk |
|---|---|---|---|
| Force-refresh ID token (getIdToken(true)) | 401 in client SDK after >1 hour session | < 5 min | Low — safe, built-in SDK call |
| Regenerate & rotate service account key | 401/403 in Admin SDK or server-side calls | 10–15 min | Medium — must redeploy all services using old key |
| Rewrite Firestore Security Rules | 403 PERMISSION_DENIED on specific collection | 15–30 min | Medium — incorrect rules lock out all users |
| Exponential backoff + jitter retry loop | 429 rate limit or 503 transient errors | 30–60 min | Low — purely additive, no breaking changes |
| Increase Firebase quota via GCP Console | Persistent 429 after backoff; production scale | 1–2 days (approval) | Low — pay-as-you-go billing increase |
| Switch to Admin SDK with service account | Long-lived backend processes getting 401 | 1–2 hrs | Low — more stable than user ID tokens on servers |
| Enable App Check | Persistent 403 due to unauthenticated abuse | 2–4 hrs | Medium — requires client SDK upgrade |
| Audit & fix CORS / allowed domains | 401 on web client, token looks valid | 15 min | Low — config change only |
Understanding Firebase Auth & API Errors
Firebase surfaces errors through two layers: the client SDK (which maps HTTP status codes to named error codes like auth/id-token-expired) and the REST/gRPC APIs underneath (which return raw 4xx/5xx HTTP codes). Understanding which layer generated the error is the first diagnostic step.
Error Code Taxonomy
| HTTP Code | Firebase Error Code | Root Cause |
|---|---|---|
| 400 | auth/invalid-email, auth/weak-password |
Malformed request payload |
| 401 | auth/id-token-expired, UNAUTHENTICATED |
Expired or missing ID token |
| 403 | PERMISSION_DENIED, auth/unauthorized-domain |
Security Rules blocked the request or domain not whitelisted |
| 429 | RESOURCE_EXHAUSTED, auth/too-many-requests |
Rate limit or quota exceeded |
| 502 | (no SDK code — raw HTTP) | Google infrastructure gateway error |
| 503 | (no SDK code — raw HTTP) | Firebase service temporarily unavailable |
Step 1: Identify Which Service Is Failing
Firebase is a suite of products. The error source matters because the fix differs:
- Firebase Authentication — sign-in, token issuance, user management
- Firestore — document reads/writes, real-time listeners
- Firebase Storage — file uploads/downloads
- Firebase Functions — HTTP callable and background triggers
- Firebase Hosting — static asset delivery and rewrites
Diagnostic command — check raw HTTP response:
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $(gcloud auth print-identity-token)" \
"https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/your-collection"
If you get 401, your token is invalid or expired. 403 means the token is valid but the Security Rules deny access. 429 means quota exhaustion.
Step 2: Diagnose 401 UNAUTHENTICATED
The most common cause is an expired ID token. Firebase ID tokens expire after 3600 seconds (1 hour). Long-running processes, server-side rendering apps, and service workers are frequent victims.
How to confirm token expiry:
# Decode the JWT payload (no library needed)
TOKEN="your.id.token.here"
payload=$(echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null || echo $TOKEN | cut -d'.' -f2 | base64 --decode 2>/dev/null)
echo $payload | python3 -m json.tool | grep -E '"exp"|"iat"|"aud"'
Compare the exp field (Unix timestamp) against date +%s. If exp < now, the token is expired.
Fix for client SDK (JavaScript):
// Force token refresh before any authenticated request
const user = auth.currentUser;
if (user) {
const token = await user.getIdToken(/* forceRefresh */ true);
// Use `token` in your Authorization header
}
Fix for Admin SDK (Node.js server):
Never use user ID tokens on the server. Use a service account:
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/serviceAccountKey.json"
Then initialize with:
import { initializeApp, cert } from 'firebase-admin/app';
const app = initializeApp({ credential: cert('/path/to/serviceAccountKey.json') });
Other 401 causes:
- Wrong project ID in client config (
firebaseConfig.projectId) - Revoked service account key — check GCP IAM Console → Service Accounts → Keys
- Missing
Authorization: Bearerheader in manual REST calls GOOGLE_APPLICATION_CREDENTIALSenv var pointing to a deleted or wrong file
Step 3: Diagnose 403 PERMISSION_DENIED
A 403 means Firebase received your token, validated it, but Security Rules rejected the operation.
Check Security Rules in Firebase Console:
Navigate to Firestore Database → Rules. A common misconfiguration:
// WRONG: Default denies everything after the 30-day trial
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false; // <-- This blocks everything
}
}
}
Use the Rules Playground in Firebase Console to simulate the failing request with your exact UID and path.
403 from Authentication (unauthorized domain):
If your web app is served from a new domain and you see:
FirebaseError: Firebase: This domain is not authorized to run this operation.
(auth/unauthorized-domain)
Go to Firebase Console → Authentication → Settings → Authorized Domains and add your domain.
Step 4: Diagnose Firebase Rate Limit (429 / RESOURCE_EXHAUSTED)
Firebase has two distinct rate-limiting systems:
- Firestore quotas (free Spark plan): 50,000 reads/day, 20,000 writes/day, 20,000 deletes/day
- Authentication rate limits: 100 sign-ins per IP per minute (default)
Identify quota exhaustion in logs:
GRPC error 8: RESOURCE_EXHAUSTED: Quota exceeded for quota metric 'datastore.googleapis.com/api/request_count'
Check current quota usage:
gcloud firestore operations list --project=YOUR_PROJECT_ID
# Or via GCP Console: APIs & Services → Quotas → search "Firestore"
Implement exponential backoff (JavaScript):
async function firestoreWithRetry(operation, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (err) {
if (err.code === 'resource-exhausted' || err.status === 429) {
const delay = Math.min(1000 * Math.pow(2, attempt) + Math.random() * 500, 32000);
console.warn(`Rate limited. Retrying in ${delay}ms (attempt ${attempt + 1})`);
await new Promise(r => setTimeout(r, delay));
} else {
throw err; // Non-retryable error
}
}
}
throw new Error('Max retries exceeded');
}
Step 5: Diagnose 502 / 503 Errors
These are transient server-side errors from Google's infrastructure. They are not caused by your code.
Immediate checks:
- Visit https://status.firebase.google.com — look for active incidents
- Check https://status.cloud.google.com for underlying GCP issues
- These errors typically self-resolve within minutes
If 503 persists beyond 10 minutes, check for:
- Firestore security rules with extremely complex queries causing server-side timeout
- Cloud Functions that exceed the 540-second timeout (returns 503 to the caller)
- Hosting rewrites pointing to a deleted Cloud Run service
Cloud Functions timeout fix:
gcloud functions deploy my-function \
--timeout=540s \
--memory=512MB \
--project=YOUR_PROJECT_ID
Step 6: Diagnose Connection Refused
This error (ECONNREFUSED or connect ECONNREFUSED) is almost always a network or environment configuration issue, not a Firebase bug.
Common causes:
- Firebase emulator is not running but
FIREBASE_EMULATOR_HOSTenv var is set - Corporate firewall blocking outbound
443to*.googleapis.com - Docker container with no egress network configured
Test connectivity:
curl -v https://firestore.googleapis.com/
nslookup firestore.googleapis.com
traceroute firestore.googleapis.com
Check for accidental emulator config:
env | grep -i firebase
env | grep -i emulator
# Unset if wrong:
unset FIREBASE_EMULATOR_HOST
unset FIRESTORE_EMULATOR_HOST
Step 7: Monitoring & Prevention
Set up Cloud Monitoring alerts for quota usage:
gcloud alpha monitoring policies create \
--notification-channels=YOUR_CHANNEL_ID \
--display-name="Firestore quota warning" \
--condition-display-name="Reads > 40000/day" \
--condition-filter='metric.type="firestore.googleapis.com/document/read_count"' \
--condition-threshold-value=40000 \
--project=YOUR_PROJECT_ID
Enable Firebase Performance Monitoring in your app to catch latency spikes before they become 503s:
import { getPerformance } from 'firebase/performance';
const perf = getPerformance(app);
Frequently Asked Questions
#!/usr/bin/env bash
# Firebase Diagnostic Script
# Usage: PROJECT_ID=my-project bash firebase-diagnose.sh
set -euo pipefail
PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project 2>/dev/null)}"
echo "=== Firebase Diagnostic Report ==="
echo "Project: $PROJECT_ID"
echo "Date: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo ""
# 1. Check gcloud auth
echo "--- Auth Status ---"
gcloud auth list --format='value(account,status)' 2>&1 | head -5
# 2. Check GOOGLE_APPLICATION_CREDENTIALS
echo ""
echo "--- Service Account Credentials ---"
if [[ -n "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]]; then
echo "GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS}"
if [[ -f "$GOOGLE_APPLICATION_CREDENTIALS" ]]; then
echo "File exists: YES"
python3 -c "import json; d=json.load(open('$GOOGLE_APPLICATION_CREDENTIALS')); print(f'Service Account: {d.get(\"client_email\", \"N/A\")}')"
else
echo "File exists: NO — this will cause 401 errors!"
fi
else
echo "GOOGLE_APPLICATION_CREDENTIALS is not set"
fi
# 3. Check emulator env vars
echo ""
echo "--- Emulator Env Vars (unexpected values cause connection refused) ---"
env | grep -iE 'emulator|firestore_host|firebase_host' || echo "No emulator vars set"
# 4. Test Firestore API connectivity
echo ""
echo "--- Firestore API Connectivity ---"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time 10 \
"https://firestore.googleapis.com/v1/projects/${PROJECT_ID}/databases/(default)/documents" \
-H "Authorization: Bearer $(gcloud auth print-access-token 2>/dev/null)" 2>/dev/null || echo "000")
echo "HTTP Status: $HTTP_CODE"
case $HTTP_CODE in
200) echo "Result: OK" ;;
401) echo "Result: 401 — token invalid or expired. Run: gcloud auth login" ;;
403) echo "Result: 403 — check Security Rules and IAM permissions" ;;
429) echo "Result: 429 — RATE LIMITED. Wait and retry with backoff" ;;
50*) echo "Result: $HTTP_CODE — transient Firebase/GCP error. Check https://status.firebase.google.com" ;;
000) echo "Result: Connection failed — check network / firewall / DNS" ;;
*) echo "Result: Unexpected status $HTTP_CODE" ;;
esac
# 5. Test Auth API
echo ""
echo "--- Firebase Auth API Connectivity ---"
AUTH_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time 10 \
"https://identitytoolkit.googleapis.com/v1/projects/${PROJECT_ID}/accounts:lookup" \
-H "Authorization: Bearer $(gcloud auth print-access-token 2>/dev/null)" \
-H "Content-Type: application/json" \
-d '{"idToken": "test"}' 2>/dev/null || echo "000")
echo "HTTP Status: $AUTH_CODE (400 is expected for dummy token — means API is reachable)"
# 6. Decode a JWT token if provided
if [[ -n "${ID_TOKEN:-}" ]]; then
echo ""
echo "--- JWT Token Analysis ---"
PAYLOAD=$(echo "$ID_TOKEN" | cut -d'.' -f2 | tr '_-' '/+' | \
awk '{while(length%4!=0) $0=$0"="; print}' | base64 -d 2>/dev/null)
echo "$PAYLOAD" | python3 -m json.tool 2>/dev/null | grep -E '"exp"|"iat"|"aud"|"sub"'
NOW=$(date +%s)
EXP=$(echo "$PAYLOAD" | python3 -c "import sys,json; print(json.load(sys.stdin).get('exp',0))" 2>/dev/null || echo 0)
if [[ $EXP -lt $NOW ]]; then
echo "Token EXPIRED at $(date -d "@${EXP}" 2>/dev/null || date -r $EXP 2>/dev/null || echo $EXP)"
else
TTLSECS=$((EXP - NOW))
echo "Token valid for ${TTLSECS}s more (expires $(date -d "@${EXP}" 2>/dev/null || echo $EXP))"
fi
else
echo ""
echo "Tip: set ID_TOKEN=<your_firebase_id_token> to analyze token expiry"
fi
echo ""
echo "=== Diagnostic complete ==="Error Medic Editorial
Error Medic Editorial is a team of senior DevOps and SRE engineers with experience operating Firebase, GCP, and AWS at scale. We write actionable troubleshooting guides grounded in production incident postmortems.
Sources
- https://firebase.google.com/docs/auth/admin/errors
- https://firebase.google.com/docs/firestore/quotas
- https://cloud.google.com/firestore/docs/troubleshoot
- https://firebase.google.com/support/troubleshooter/auth
- https://stackoverflow.com/questions/37876725/firebase-permission-denied-missing-or-insufficient-permissions
- https://github.com/firebase/firebase-js-sdk/issues/1674
- https://status.firebase.google.com