@abraham/firebase-wif
v0.2.1
Published
Set up and check Firebase deploys from GitHub Actions with Workload Identity Federation
Readme
firebase-wif
Deploy to Firebase from GitHub Actions with Workload Identity Federation (OIDC), without service account keys or FIREBASE_TOKEN.
- CLI:
npx @abraham/firebase-wif statuschecks the Google Cloud project and GitHub repo;setupcreates or repairs everything, and is safe to re-run. - GitHub Action:
abraham/firebase-wifturns the job's OIDC token into Google credentials fornpx firebase-tools,FirebaseExtended/action-hosting-deploy, andw9jds/firebase-action. - Node API: the same engine as a library, for project CLIs that want their own UX.
Quick start
Sign in to Google Cloud and GitHub:
gcloud auth login gh auth loginIn your repo, run setup. It reads the project from
.firebasercand the repo from the git remote, prints a plan, and asks before changing anything:npx @abraham/firebase-wif setupAdd the Action to your deploy workflow, before the deploy step:
permissions: contents: read id-token: write jobs: deploy: runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v7 - uses: abraham/[email protected] with: workload_identity_provider: ${{ vars.FIREBASE_WIF_PROVIDER }} service_account: ${{ vars.FIREBASE_WIF_SERVICE_ACCOUNT }} - run: npx firebase-tools deploy --only hosting --non-interactive
Setup creates a workload identity pool and provider that only trust this repository, a deploy service account with the roles for what firebase.json deploys (Hosting only by default), the repo variables the Action reads, and a production environment that only the default branch can deploy to. When a workflow deploys to preview channels, it also creates a preview environment that any branch can deploy to. Check it any time with npx @abraham/firebase-wif status.
Usage
The job needs id-token: write. Pull requests from forks can't get OIDC tokens, so guard those jobs with if: github.event.pull_request.head.repo.full_name == github.repository.
npx firebase-tools
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v7
- run: npm ci && npm run build
- uses: abraham/[email protected]
with:
workload_identity_provider: ${{ vars.FIREBASE_WIF_PROVIDER }}
service_account: ${{ vars.FIREBASE_WIF_SERVICE_ACCOUNT }}
- run: npx firebase-tools deploy --only hosting --non-interactivePreview channels work the same way, from a pull request job in the preview environment:
on: pull_request
jobs:
preview:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
environment: preview
permissions:
contents: read
id-token: write
steps:
# checkout, build, and abraham/firebase-wif as above
- id: deploy
run: |
npx firebase-tools hosting:channel:deploy "pr-${{ github.event.number }}" \
--expires 7d --json > deploy.json
echo "url=$(jq -r '.result[].url' deploy.json)" >> "$GITHUB_OUTPUT"FirebaseExtended/action-hosting-deploy
- id: wif
uses: abraham/[email protected]
with:
workload_identity_provider: ${{ vars.FIREBASE_WIF_PROVIDER }}
service_account: ${{ vars.FIREBASE_WIF_SERVICE_ACCOUNT }}
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ steps.wif.outputs.credentials_json }}
projectId: ${{ steps.wif.outputs.project_id }}w9jds/firebase-action
It runs in a container, so pass the credentials JSON instead of a file path:
- id: wif
uses: abraham/[email protected]
with:
workload_identity_provider: ${{ vars.FIREBASE_WIF_PROVIDER }}
service_account: ${{ vars.FIREBASE_WIF_SERVICE_ACCOUNT }}
- uses: w9jds/[email protected]
with:
args: deploy --only hosting
env:
GCP_SA_KEY: ${{ steps.wif.outputs.credentials_json }}
PROJECT_ID: ${{ steps.wif.outputs.project_id }}Inputs
| Input | Required | Default | Description |
| ---------------------------- | -------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| workload_identity_provider | yes | | Full provider resource name, usually ${{ vars.FIREBASE_WIF_PROVIDER }} |
| service_account | yes | | Service account email, usually ${{ vars.FIREBASE_WIF_SERVICE_ACCOUNT }} |
| project_id | no | .firebaserc default | Firebase project ID; falls back to the provider's project number |
| working_directory | no | . | Where to look for .firebaserc |
| audience | no | https://iam.googleapis.com/<provider> | For providers with custom allowedAudiences |
| verify | no | true | Do a test token exchange so problems fail here, with a fix, instead of inside the deploy |
| export_environment | no | true | Export GOOGLE_APPLICATION_CREDENTIALS, CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE, GOOGLE_GHA_CREDS_PATH, GOOGLE_CLOUD_PROJECT, GCLOUD_PROJECT, and CLOUDSDK_CORE_PROJECT |
Outputs
| Output | Description |
| ----------------------- | -------------------------------------------------------------------------------- |
| credentials_file_path | Path to the external_account credentials file in RUNNER_TEMP |
| credentials_json | The file's contents, masked, for actions that take a service account JSON string |
| project_id | The resolved project ID |
| service_account | The service account email |
The credentials file is written outside the workspace with mode 0600 and deleted when the job ends. It holds the job's OIDC request token, so never upload it as an artifact.
CLI
npx @abraham/firebase-wif status [options] # read-only checks (default)
npx @abraham/firebase-wif setup [options] # create or repairUseful options (--help lists them all):
--project <id|alias>,--repo <owner/name>: override what's found in.firebasercand the git remote.--preset hosting|firestore|storage|functions(repeatable) and--role <role>: the deploy roles. By default the presets come from the keys infirebase.json.--dry-run,--yes: print the plan only, or apply without asking.--prune: remove roles that are no longer requested from a service account firebase-wif created.--environment <name>,--any-branch: the GitHub environment to create, and let any branch (and so pull requests) deploy to it instead of only the default branch.--preview-environment <name>,--no-preview-environment: the environment any branch can deploy to, for preview channels. By default it's created when a workflow deploys to a preview channel, named after the environment those jobs use, orpreview.--adopt: reuse a setup made bysetup-wif.shor hoverboard (GCP_WIF_PROVIDER/GCP_DEPLOY_SAorWIF_PROVIDER/DEPLOY_SERVICE_ACCOUNTvariables).--all-aliases: run for every project in.firebaserc. The first project holds the pool, provider, and service account; the others only get APIs and deploy roles.--detailed: show resource names, emails, and role IDs instead of plain-language summaries.--json: machine-readable output.
Exit codes: 0 ok, 1 a check failed, 2 usage error, 3 missing credentials or permissions.
Node API
import { setup, status } from '@abraham/firebase-wif';
const report = await status({ projectId: 'my-project', repo: 'me/site' });
if (!report.ok) console.log(report.changes.map((change) => change.description));
await setup({ projectId: 'my-project', repo: 'me/site', confirm: async (plan) => plan.changes.length < 10 });The library never prints, prompts, or reads the environment or filesystem on its own. Other entry points:
@abraham/firebase-wif/credentials:createCredentialsConfig()andverifyCredentials(), what the Action uses.@abraham/firebase-wif/presets: the role presets.@abraham/firebase-wif/gh: a GitHub client that goes through a signed-inghCLI.@abraham/firebase-wif/testing: in-memory Google Cloud and GitHub fakes for testing integrations.
Versioning
The npm package and the Action share a version. Every release gets one immutable v0.y.z tag; there are no v0.y or v0 tags that move. Pin the Action to a full version and let Dependabot update it. While on 0.x, a minor release may change the API, CLI flags, check IDs, or Action inputs; pin @abraham/firebase-wif@~0.y if you need stability.
Releasing
- Update
versioninpackage.json(for example withnpm version 0.2.1 --no-git-tag-version), update versions in documentation, and merge the change tomainthrough a pull request. - On GitHub, create a release from
mainwith a new tag namedvplus that version, such asv0.2.1.
Publishing the release runs the publish workflow, which checks that the tag matches package.json and publishes the package to npm. The tag is what Action users pin to.
License
MIT
