You run firebase deploy locally or in CI and it stops with one of these:
Error: HTTP Error: 403, The caller does not have permission
Error: Request to https://firebase.googleapis.com/... had HTTP Error: 403
Or, more bluntly:
Error: Failed to authenticate, have you run firebase login?
Fastest fix: 90% of the time you are logged into the wrong Google account. Run firebase login:list, confirm the email that actually owns the project, switch with firebase login:use you@company.com (or pass --account you@company.com to the deploy command), then redeploy. If the account is right but still 403, an admin needs to grant you roles/firebasehosting.admin (and roles/cloudfunctions.admin + roles/iam.serviceAccountUser for Functions).
The error is almost never in the firebase deploy command itself. It is in the identity chain behind it: your local Google account -> which project that account is pointed at -> that account’s IAM roles on Google Cloud. Break any link and you get permission denied. Work down the chain in order and you will find the broken link in a couple of minutes.
Which bucket are you in?
Match your exact error text to a cause before you start changing things.
| Error text you see | Most likely cause | Jump to |
|---|---|---|
Failed to authenticate, have you run firebase login? | Not logged in, or token expired | Step 2 |
403 ... caller does not have permission (local) | Wrong account, or missing IAM role | Steps 2-3 |
403 only in CI, works locally | Bad/expired service-account creds or deprecated --token | Step 5 |
Failed to get Firebase project <id> ... check permissions | Wrong project ID in .firebaserc, or no access | Step 4 |
API ... has not been used ... or it is disabled | Required API not enabled | Step 6 |
principalType is not allowed by the organization policy | Org policy blocks external/personal accounts | Cause 6 below |
Common causes
Ordered by hit rate, highest first.
1. Logged into a personal account; project lives under a work account
The most common case by far. firebase login defaulted to personal@gmail.com, but the project sits in you@company.com’s Google Cloud organization, and the personal account has zero access there.
How to spot it: firebase login:list shows every authorized account and marks the active one. Compare that email to whoever owns the project in the Firebase console.
2. Account lacks the Firebase Hosting Admin role
The account can see the project but cannot deploy. Classic for new hires: an admin granted Viewer but forgot the deploy roles.
How to spot it: Open Google Cloud IAM, select the project, find your email, and review the role list. If you only see Viewer or Browser, that is your problem.
3. Wrong project ID in .firebaserc
{
"projects": {
"default": "my-project-prod"
}
}
If the real project ID is my-project-prod-x9q, the CLI tries to deploy to a project that either does not exist or that you cannot access — both surface as a permission or “failed to get project” error. Project IDs are globally unique and Google often appends a random suffix at creation, so the human-friendly name and the real ID rarely match.
How to spot it: firebase projects:list shows the real Project ID column. Compare it to .firebaserc.
4. CI uses an expired, deleted, or under-privileged credential
CI authenticates via GOOGLE_APPLICATION_CREDENTIALS (a service-account key) or the legacy FIREBASE_TOKEN. The key was deleted, rotated, or had its roles revoked — or you are still using firebase login:ci, which is now deprecated (see Step 5).
How to spot it: CI logs show 401/403, but a local deploy of the same project works.
5. A required Google Cloud API is not enabled
Deploying Hosting needs firebasehosting.googleapis.com. Deploying Functions pulls in a lot more, because Cloud Functions are now 2nd-gen by default and run on Cloud Run — that means run.googleapis.com, eventarc.googleapis.com, pubsub.googleapis.com, cloudbuild.googleapis.com, and artifactregistry.googleapis.com all have to be on. The CLI offers to enable them interactively, but in --non-interactive CI runs nobody confirms the prompt.
How to spot it: The error contains API ... has not been used in project ... before or it is disabled, or Permission denied to enable service [artifactregistry.googleapis.com].
6. Org policy blocks personal accounts
Some Google Cloud organizations enforce a Domain Restricted Sharing policy (“External members not allowed”). A gmail.com account can be invited but the binding is rejected, so it can never deploy.
How to spot it: The error contains principalType is not allowed by the organization policy. The fix is on the admin side: either use an account inside the org’s domain, or have an org admin add an exception in Organization Policies.
Shortest path to fix
Step 1: Audit the identity chain
# 1. Which accounts are authorized, and which is active
firebase login:list
# 2. Which projects the active account can actually access
firebase projects:list
# 3. The project alias in this folder's .firebaserc
cat .firebaserc
# 4. The active deploy target
firebase use # prints the current project
Cross-check, top to bottom: the active account -> that project shows up in projects:list -> the ID in .firebaserc is in that list -> firebase use points at the project you actually mean to deploy to. The first line that fails is your culprit.
Step 2: Log in with the right account
If you have one account and it is just stale:
firebase logout
firebase login # pick the correct account in the browser
firebase login:list # confirm the active email
If you juggle several Google accounts, do not keep logging out and back in. Authorize each one once, then switch per directory or per command:
firebase login:add # add a second account to the CLI
firebase login:use you@company.com # set the default account for THIS project dir
To override the account for a single command, use --account (the account must already be added):
firebase deploy --account you@company.com --project my-prod
Step 3: Have an admin grant the IAM roles
These are the minimum predefined roles to deploy Hosting plus Functions, as of June 2026:
- Firebase Hosting Admin (roles/firebasehosting.admin) # Hosting deploys
- Cloud Functions Admin (roles/cloudfunctions.admin) # Functions deploys
- Service Account User (roles/iam.serviceAccountUser) # REQUIRED with Functions
- Firebase Admin (roles/firebase.admin) # blunt all-in-one
Two things people miss:
- Functions need the pair, not just
cloudfunctions.admin. Withoutroles/iam.serviceAccountUser, the deploy fails because the CLI cannot act as the runtime service account. Grantingcloudfunctions.adminalone is the single most common “I added the role and it still fails” case. - Custom roles cannot control Firebase Hosting access — Google only honors the predefined
roles/firebasehosting.admin/roles/firebasehosting.viewerfor Hosting, so do not waste time building a custom role for it.
An admin grants these at IAM -> select the project -> Grant Access -> enter your email -> add the roles -> Save. IAM changes propagate within a minute or two; if a fresh grant still 403s, wait and retry once.
Step 4: Fix .firebaserc
firebase use --add # interactive picker: choose project, give it an alias
Or edit the file by hand, using the real IDs from firebase projects:list:
{
"projects": {
"default": "my-project-prod-x9q",
"staging": "my-project-staging-a3p"
}
}
Then point at the right environment and deploy:
firebase use staging
firebase deploy --only hosting
Step 5: Authenticate CI with a service account (not a personal token)
Never run firebase login with a personal account in CI. There are two correct options.
Option A (recommended): Workload Identity Federation — keyless. No long-lived key file to leak or rotate. Configure a workload identity pool that trusts your CI provider, bind it to a service account that has the roles from Step 3, then in GitHub Actions:
permissions:
contents: read
id-token: write # required for keyless OIDC
steps:
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123/locations/global/workloadIdentityPools/gh/providers/gh
service_account: deployer@my-project-prod.iam.gserviceaccount.com
- run: firebase deploy --project my-project-prod --non-interactive
Option B: Service-account key file. Simpler to set up, but you own a long-lived secret.
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GCP_SA_KEY }}
steps:
- run: |
echo "$GOOGLE_APPLICATION_CREDENTIALS" > "$RUNNER_TEMP/sa.json"
export GOOGLE_APPLICATION_CREDENTIALS="$RUNNER_TEMP/sa.json"
firebase deploy --project my-project-prod --non-interactive
Do not reach for firebase login:ci / --token / FIREBASE_TOKEN anymore — that auth method is deprecated and will be removed in a future major version of firebase-tools, and Google’s own docs now tell you to use a service account instead. If you still see it in an old pipeline, migrate to Option A or B.
One subtle CI gotcha: firebase init hosting:github wires up a service account for you, but it is provisioned with Hosting permissions only. If the same workflow also deploys Functions, Firestore rules, or Storage rules, you must grant that service account the extra roles from Step 3 yourself, or it will 403 on those targets while Hosting succeeds.
Step 6: Enable the required APIs
gcloud services enable \
firebasehosting.googleapis.com \
cloudfunctions.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
run.googleapis.com \
eventarc.googleapis.com \
pubsub.googleapis.com \
--project=my-project-prod
run, eventarc, and pubsub are only needed if you deploy Functions (2nd-gen), but enabling them costs nothing and saves a second round trip. You can also flip them on in the API Library. Note that enabling an API itself requires roles/serviceusage.serviceUsageAdmin (or Editor/Owner) — if gcloud services enable 403s, that is the missing permission, not a deploy role.
How to confirm it’s fixed
Run a dry run first so you fail fast without touching production:
firebase deploy --only hosting --dry-run
A clean dry run means auth, project mapping, and roles all check out. Then do the real deploy. If you ever need the full request/response trail to see exactly which API call returned 403, add --debug:
firebase deploy --debug
The last 403 line in that output names the exact API and resource that rejected you, which maps directly back to the role or API you are missing.
Prevention
- Document the required IAM roles on your day-one onboarding wiki so new hires get them on the first try.
- Keep personal and work projects on separate Google accounts; isolate them with browser profiles so
firebase loginnever grabs the wrong one. - Commit
.firebasercto git with an alias per environment so the team shares one source of truth. - In CI, prefer Workload Identity Federation; if you must use a key, rotate it on a schedule.
- Prefer
firebase use --addand aliases over hand-editing.firebaserc— more reproducible. - Always
firebase deploy --only hosting --dry-runbefore a real production deploy.
FAQ
Why does it work locally but 403 in CI? Different identity. Locally you deploy as your Google account; CI deploys as a service account (or an OIDC-federated identity). The service account is missing a role from Step 3, or its key/credentials expired. Check the CI auth step and the service account’s IAM roles, not your own.
I added roles/cloudfunctions.admin and Functions still won’t deploy. Why?
Functions deploys also require roles/iam.serviceAccountUser on the runtime service account, so the deployer is allowed to “act as” it. Add that role and retry. This pair requirement trips up almost everyone.
How do I switch the active Firebase account without logging out?
firebase login:add to authorize the second account once, then firebase login:use <email> to set the default for the current project directory, or pass --account <email> on a single command.
Is FIREBASE_TOKEN / firebase login:ci still safe to use?
It still works for now but is officially deprecated and slated for removal in a future major release of firebase-tools. Migrate CI to a service account (key file) or, better, Workload Identity Federation.
My project name and .firebaserc ID don’t match — is that the bug?
Probably. The display name is not the project ID. Project IDs are globally unique and often carry a random suffix (my-app-prod-x9q). Run firebase projects:list and copy the exact ID into .firebaserc.
The error mentions an organization policy. Can I fix it myself?
No. principalType is not allowed by the organization policy is enforced at the org level. Either use an account inside the org’s domain, or ask an org admin to add an exception in Organization Policies.