Firebase Token Toolkit
Firebase Token Toolkit is a desktop app for getting the Firebase auth tokens you need while developing and testing a backend. Pick a user, click Generate, and you have a real ID token for that user, with its claims decoded underneath and a Copy button next to it. Paste it into Postman, curl or your own tests to call any API that verifies Firebase ID tokens.
It covers five jobs, each on its own tab:
| Tab | What it does |
|---|---|
| UID -> Custom Token | Signs a custom token for a user with your service-account key. |
| UID -> ID Token | Signs a custom token and immediately exchanges it, giving you an ID token in one click. |
| Custom -> ID Token | Exchanges a custom token you already have for an ID token. |
| User Custom Claims | Reads and edits the custom claims stored on a user record. |
| App Check | Exchanges an App Check debug token for an App Check token. |
A Users panel on the left lists the project’s users and searches them by email, phone or UID, so you rarely need to look a UID up by hand. Profiles keep the settings for several Firebase projects side by side, and each profile can hold the project’s web, Android and iOS apps, including API keys that are restricted to one app.
The app is a single native binary for Linux, Windows and macOS. It talks only to Google’s endpoints, only when you click something, and never writes your private key or the tokens it mints to disk.
Where to start
- Install the app.
- Collect what you need from your Firebase project: usually just a service-account key. The app can read the project’s web, Android and iOS apps itself.
- Follow the Quick start to your first ID token.
Have a specific question? The FAQ answers the common ones.
Use a development project. A service-account key can sign in as any user in its project, and the app shows real tokens on screen. Read the Security page before pointing it at anything that matters.
If you find this useful, please consider starring the project on GitHub, it really helps!
Installation
Download the archive for your platform from the latest release. There is no installer and nothing else to install: the app is a single native binary with no Node, webview, or Java runtime behind it.
| Platform | Archive | Requirements |
|---|---|---|
| Linux | firebase-token-toolkit-<version>-x86_64-linux.tar.gz | x86_64, glibc 2.35 or newer (Ubuntu 22.04 and later) |
| Windows | firebase-token-toolkit-<version>-x86_64-windows.zip | x86_64 |
| macOS | firebase-token-toolkit-<version>-universal-macos.dmg | macOS 11 or newer, Apple Silicon or Intel |
Each release also publishes SHA256SUMS. To check a download before running
it on Linux or macOS:
sha256sum --check --ignore-missing SHA256SUMS # macOS: shasum -a 256 -c --ignore-missing SHA256SUMS
Linux
tar xzf firebase-token-toolkit-*-x86_64-linux.tar.gz
cd firebase-token-toolkit-*-x86_64-linux
./firebase-token-toolkit
Two parts of the app lean on the desktop environment:
- The Browse… button opens the file chooser through an
XDG desktop portal.
GNOME, KDE and most other desktops ship one. On a bare window manager,
install
xdg-desktop-portal-gtkor an equivalent, or the picker will not open. - The Copy buttons write to the X11 clipboard. Under Wayland that needs XWayland, which nearly every Wayland session runs by default. If a copy fails, the error appears next to the button instead of failing silently.
Windows
Unzip the archive and run firebase-token-toolkit.exe.
The binary is not code-signed, so SmartScreen may stop it on first launch. Choose More info, then Run anyway. The C runtime is linked statically, so you do not need the Visual C++ Redistributable.
macOS
Open the .dmg and drag Firebase Token Toolkit into Applications.
The app is ad-hoc signed but not notarized, because the project has no Apple Developer account. Gatekeeper therefore blocks the first launch. Either right-click the app, choose Open, then Open again in the dialog, or clear the quarantine flag from a terminal:
xattr -dr com.apple.quarantine "/Applications/Firebase Token Toolkit.app"
Updating
Download the new archive and replace the old binary. Your profiles and settings live outside the app (see Settings and storage), so they carry over.
Building from source
If there is no build for your platform, or you would rather compile it yourself, see Building and contributing.
Preparing your Firebase project
The app needs up to three things from your Firebase project: a service-account key, the project ID, and the details of the apps you test with. Which ones depend on the tabs you plan to use:
| Tab | Service-account key | Project ID | App’s API key | App ID |
|---|---|---|---|---|
| UID -> Custom Token | ✔ | for the Users panel | ||
| UID -> ID Token | ✔ | for the Users panel | ✔ | |
| Custom -> ID Token | ✔ | |||
| User Custom Claims | ✔ | ✔ | ||
| App Check | ✔ | ✔ | ✔ |
Use a development project. Nothing in the app needs production credentials, and a service-account key can sign in as any user in its project. See Security.
Service-account key
- Open the Firebase console and pick your project.
- Go to Project settings → Service accounts.
- Click Generate new private key, then Generate key. A JSON file downloads.
Keep the file somewhere stable. The app saves its path, not its contents, and reads it again on every launch.
The key the console generates belongs to the project’s
firebase-adminsdk-…@<project>.iam.gserviceaccount.com account, which already
has the access the app needs. If you use a different service account, it needs
permission to read and update Firebase Authentication users for the Users
panel and the User Custom Claims tab, for example the Firebase
Authentication Admin role (roles/firebaseauth.admin). Signing custom tokens
happens locally with the key and needs no extra role.
Project ID
The app fills this in from the service-account file, so you rarely need to type it. It is also shown under Project settings → General.
Apps: web, Android and iOS
Each profile holds a list of the project’s apps, and the tabs that need an API key use the one you select. Any app registered in the project works, whatever its platform.
The quick way: with the service-account key loaded, click Load apps in the top bar. The app reads every web, Android and iOS app in the project through the Firebase Management API and fills in each app’s App ID, API key, package name and SHA-1 (Android) or bundle ID (iOS). See The top bar.
The service account needs to be allowed to read Firebase apps
(firebase.clients.list and firebase.clients.get). The key the console
generates already is; for another account, the Firebase Viewer role
(roles/firebase.viewer) covers it.
By hand: under Project settings → General → Your apps, each app
shows its App ID, for example 1:316659987175:android:b9522e663c7b203d12a842.
Its API key is in the config file offered there (google-services.json,
GoogleService-Info.plist, or the web snippet’s apiKey). The project-wide
Web API Key on the same page also works for any app, as long as it has no
application restriction.
API keys restricted to an app
Google Cloud lets you restrict a key to particular Android apps (package name plus signing-certificate SHA-1) or iOS apps (bundle ID). Google accepts such a key only from a request that names the app, the way the Firebase SDKs do. The app sends those details for you, taken from the selected app:
| App type | Sent with the request |
|---|---|
| Web | the API key only |
| Android | X-Android-Package (package name) and X-Android-Cert (SHA-1) |
| iOS | X-Ios-Bundle-Identifier (bundle ID) |
So for a restricted key, make sure the app’s Package and SHA-1, or Bundle ID, match one of the entries in the key’s restriction. Load apps fills them from Firebase, using the first SHA-1 certificate registered for an Android app; if the key allows a different certificate, paste that one in.
If you have restricted which APIs a key may call, allow at least the Identity Toolkit API, and the Firebase App Check API if you use the App Check tab.
App Check debug token (App Check only)
The App Check tab exchanges a debug token you have registered for the app. This works the same for web, Android and iOS apps:
- Register the app with App Check under App Check → Apps if you have not already.
- Open the app’s overflow menu (⋮) and choose Manage debug tokens.
- Click Add debug token, give it a name, and either generate a token or paste one your client printed (the browser console on the web, Logcat on Android, the Xcode console on iOS). Copy the value; it is a UUID.
Treat the debug token like a password. Anyone holding it can get valid App Check tokens for your app.
Quick start
This walk-through goes from a fresh install to an ID token for one of your users. You need a service-account key file; see Preparing your Firebase project if you do not have one yet. The app reads everything else from the project.
1. Open the app
On first launch the status indicator reads not configured in red, and the tabs ask you to load a service account.
2. Load the service-account key
Click Browse… next to Service account and choose the JSON key file.
The path appears in the top bar, Project ID fills itself in from the key, and the indicator changes to SA only. A green Service account loaded message confirms it; click dismiss to hide it.
3. Load the project’s apps
Click Load apps. The app lists the project’s web, Android and iOS apps and fills in each one’s App ID and API key. Pick the app you want to test as from the App dropdown; the indicator turns green and reads ready.
If the service account is not allowed to read apps, enter an app by hand instead: + Add app, choose its type, and paste its API key and App ID. See The top bar.
4. Load your users
Click Load users in the Users panel. The app fetches up to 5,000 users and lists each one by email (or phone number), display name and UID.
5. Generate an ID token
- Click a user in the list. Their UID appears under Selected UID in the top bar.
- Open the UID -> ID Token tab.
- Click Generate.
The ID token appears in a box with a Copy button. Under it are a shortened refresh token, the time until the token expires, and a collapsible Decoded JWT claims table.
Click Copy and use the token wherever your backend expects one, for example:
curl -H "Authorization: Bearer <paste token here>" https://localhost:8080/api/me
6. Keep the setup for next time
The service-account path, Project ID and app list are saved automatically. API keys are saved only if you tick remember API keys & debug tokens; without it, click Load apps again after each launch, or paste the key. See Settings and storage for what that writes to disk.
Next steps
- Add custom claims to a token, or store claims on the user so every token carries them.
- Set up profiles if you work with more than one Firebase project.
The top bar
Everything the tabs need comes from the top bar. Its values belong to the active profile, so switching profiles swaps them all at once. The first rows hold the profile and service account; the last two hold the selected app.
Service account
Browse… opens a file chooser for the service-account JSON key. When the key loads, a green Service account loaded message appears, and if Project ID is empty it is filled in from the key.
Clear unloads the key and clears the selected user. It also clears Project ID, but only if the key filled that value in and you have not edited it since.
The app stores the file’s path, not its contents, and reads the key again on every launch. If the file has moved, the profile starts with no service account loaded.
Status indicator
The indicator to the right of the service account shows what the app can do right now:
| Indicator | Meaning | Tabs available |
|---|---|---|
| not configured (red) | No service account loaded | Custom -> ID Token and App Check only, given an app with an API key |
| SA only (yellow) | Service account loaded, the selected app has no API key | UID -> Custom Token, User Custom Claims, the Users panel |
| ready (green) | Service account, and the selected app has an API key | All tabs (App Check also needs the app’s App ID) |
Project ID
The Firebase project ID, for example fir-token-toolkit-demo. The Users panel,
User Custom Claims and App Check use it to build their requests. It normally
comes from the key file, but you can edit it.
remember API keys & debug tokens
Off by default. When ticked, every app’s API key and App Check debug token are saved to disk in plaintext, for every profile. When unticked, they are kept in memory only and cleared from every profile the next time the app saves. App names, App IDs and the Android and iOS identifiers are always saved: they are not secret, and they ship inside every build of the app. See Settings and storage.
Apps
A profile holds a list of the project’s apps, and the tabs that call Google with an API key (UID -> ID Token, Custom -> ID Token and App Check) use the one selected in the App dropdown.
| Control | What it does |
|---|---|
| App dropdown | Selects the app, listed as name (type). Switching clears the output of the three API-key tabs, but not the user list or the other tabs. |
| name | Renames the selected app. |
| + Add app | Adds an empty Web, Android or iOS app and selects it. |
| Remove | Removes the selected app. Removing the last one leaves an empty web app. |
| Load apps | Imports the project’s apps from Firebase. Needs the service account and Project ID. |
| Type | Web, Android or iOS. Decides which identifying headers go with the API key. |
| API key | The key sent with requests for this app. Masked. |
| App ID | The app’s Firebase App ID. Pasting one sets Type to match, and renames an app still carrying a default name such as Web app. |
| Package, SHA-1 | Android only: the package name and signing-certificate SHA-1. |
| Bundle ID | iOS only. |
Load apps matches apps on their App ID, so running it again updates the list rather than duplicating it. It refreshes each app’s type, package, bundle ID and API key from Firebase, but keeps a name or SHA-1 you changed yourself.
The Package and SHA-1, or Bundle ID, matter only for an API key restricted to an Android or iOS app; see API keys restricted to an app. A SHA-1 that is not 40 hex digits (colons are fine) is marked invalid in yellow and is not sent.
Selected UID
Once you pick a user in the Users panel, a Selected UID row shows their UID and, in grey, their email or phone and display name. The UID-based tabs act on this user.
Profiles
A profile holds one project’s settings: service-account path, Project ID, and the project’s apps with their API keys and App Check debug tokens. Keep one per Firebase project, for example Dev, Staging and Production, and switch between them from the Profile dropdown.
Managing profiles
| Control | What it does |
|---|---|
| Profile dropdown | Switches to another profile. Unnamed profiles are listed as (unnamed #N). |
| name | Renames the active profile as you type. |
| + New | Creates an empty profile called Profile N and switches to it. |
| Delete | Removes the active profile straight away, without asking first. |
If you delete the only profile, an empty Default profile replaces it.
Delete is greyed out when there is nothing to delete: a single profile with
no service account and no API key.
What switching does
Switching profiles gives you a clean slate. The app:
- loads the new profile’s service account from its saved path,
- clears the user list and the selected user,
- discards the cached OAuth access token,
- clears the output of every tab.
Nothing from one project leaks into another, so a token on screen always belongs to the profile shown in the dropdown.
Saving
Profile names, service-account paths, Project IDs and app lists are always saved. API keys and debug tokens are saved only while remember API keys & debug tokens is ticked, and that checkbox applies to every profile, not just the active one. See Settings and storage.
The Users panel
The panel on the left lists the project’s Firebase Authentication users so you can pick one instead of copying UIDs around. It appears on the tabs that act on a user: UID -> Custom Token, UID -> ID Token and User Custom Claims.
It needs a loaded service account and a Project ID. Without them it shows Load a service account and set Project ID to browse users.
Loading users
Click Load users. The app fetches users 1,000 at a time, up to 5,000 in total, and the button becomes Refresh. A counter shows how many users are loaded and how many match the search box.
Each row shows:
- the user’s email, or their phone number if they have no email, or
(no email/phone), - their display name after a dash, if they have one,
- their UID on the second line.
Click a row to select that user. The row is highlighted and the user appears under Selected UID in the top bar.
Filtering and looking up
The search box does two different things.
Typing filters the loaded list. Matching is case-insensitive and covers
email, phone, display name and UID, so example keeps every @example.com
user and +1555 finds phone users. Nothing is sent to Google.
Pressing Enter or clicking Lookup asks Firebase directly. Use it for users outside the loaded 5,000, or without loading the list at all. The query must be a complete value, and its form decides what is searched:
| Query starts with or contains | Looked up as | Example |
|---|---|---|
starts with + | Phone number | +15555550123 |
contains @ | Email (case does not matter) | alice@example.com |
| anything else | UID | Gx9uWZg06RdT9wLNWM0w9VdRNqv1 |
A user found this way is selected and added to the top of the list. If nobody matches, the panel says so:
Privacy
The list shows real personal data from your project: emails, phone numbers and display names. Keep that in mind before sharing your screen or a screenshot.
UID -> Custom Token
Signs a Firebase custom token for the selected user with your service-account key. Signing happens on your machine; nothing is sent over the network.
Needs: a service account and a selected user.
Generating a token
- Pick a user in the Users panel.
- Optionally, enter claims in Custom claims (optional JSON).
- Click Generate.
The token appears under Custom Token with a Copy button. Open Decoded JWT claims to see what was signed.
A custom token is valid for one hour. It is not an ID token: a client signs in
with it, using signInWithCustomToken in a Firebase SDK, and receives an ID
token back. To skip that step, use UID -> ID Token.
Adding claims to one token
Claims typed here go into the token’s claims field and are carried into the
ID token that the custom token is exchanged for. They are not stored on the
user, so the next token you generate starts without them.
The box must hold a JSON object, such as:
{"tier": "beta", "beta_features": ["new-checkout"]}
To give a user claims that appear in every token they receive, use User Custom Claims instead.
What the token contains
| Claim | Value |
|---|---|
iss, sub | The service account’s email address |
aud | https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit |
uid | The selected user’s UID |
iat, exp | Issue time, and expiry one hour later |
claims | Your optional claims, if any |
UID -> ID Token
Gets a Firebase ID token for the selected user in one click. The app signs a
custom token locally, exchanges it with Google through signInWithCustomToken,
and shows the resulting ID token.
Needs: a service account, an app with an API key, and a selected user. The exchange uses the selected app’s key, so you can check that an Android or iOS app’s restricted key works.
Generating a token
- Pick a user in the Users panel.
- Optionally, enter claims in Custom claims (optional JSON). They are added to this token only.
- Click Generate.
Under the token you get:
- Refresh token: a shortened view of the refresh token Google returned. It is shown for reference only; there is no button to copy it.
- Expires in: the token’s lifetime in seconds, normally
3600s. - Decoded JWT claims: everything the ID token asserts. Claims stored on the user with User Custom Claims appear here alongside the standard ones.
Using the token
Send it as a bearer token to anything that verifies Firebase ID tokens, such as
your backend’s verifyIdToken call or a Firebase security rule test:
curl -H "Authorization: Bearer $ID_TOKEN" https://localhost:8080/api/me
The token is a real credential for that user until it expires. Treat it like a password.
Signing in as the user
Exchanging a custom token is a real sign-in. The user’s Signed in date in
the Firebase console updates, auth_time in the token is set to now, and the
firebase claim records "sign_in_provider": "custom". If your backend
treats the sign-in provider specially, or you track sign-in activity, bear that
in mind.
Custom -> ID Token
Exchanges a custom token you already have for an ID token, by calling
accounts:signInWithCustomToken. Use it to check a custom token minted
somewhere else, such as by your own backend, or one copied from the
UID -> Custom Token tab.
Needs: an app with an API key. No service account is required, so this tab works in a profile that only has an app’s key.
Exchanging a token
- Paste the token into Custom token.
- Click Exchange.
The result shows the ID token with a Copy button, Expires in, and the
decoded claims. Claims carried in the custom token, such as tier above, show
up next to the ones stored on the user (plan and role).
Unlike UID -> ID Token, this tab does not show the refresh token.
When the exchange fails
Errors appear in red under the button, with Google’s message passed through. The common ones:
INVALID_CUSTOM_TOKEN : Invalid assertion format. 3 dot separated segments required.means the paste is incomplete.INVALID_CUSTOM_TOKENwith other text usually means the token has expired. Custom tokens last one hour.CREDENTIAL_MISMATCHmeans the token was signed by a different project from the one the API key belongs to.
See Troubleshooting for more.
User Custom Claims
Reads and edits the custom claims stored on a user’s record (customAttributes
in the Identity Toolkit API). Unlike the claims box on the token tabs, these
are permanent: Firebase adds them to every ID token the user gets from now
on, until you change them.
Needs: a service account, a Project ID, and a selected user.
Viewing a user’s claims
Select a user and click Load current. The editor fills with their claims, and the message says either Loaded current custom claims. or Loaded — user has no custom claims set.
Pretty-print reformats the JSON with indentation and sorted keys. If the
JSON is invalid, it reports Invalid JSON: and the parser’s message instead.
Selecting a different user clears the editor, so you cannot accidentally save one user’s claims onto another.
Changing claims
- Click Load current so you start from what is stored.
- Edit the JSON. It must be an object, for example
{"role": "admin", "plan": "pro", "level": 3}. - Click Save.
The editor replaces the user’s claims entirely; it does not merge. Any key you remove from the JSON is removed from the user.
The 1,000-byte limit
Firebase rejects claims larger than 1,000 bytes. A counter under the editor measures the claims compactly serialized, which is how Firebase counts them, so whitespace from Pretty-print does not count:
- grey
39 / 1000 bytes serialized: within the limit, - red
1040 / 1000 bytes (Firebase will reject): too large; Save refuses with an error, - yellow
(invalid JSON — fix before saving).
Keep claims small. They travel in every token and every request, and they suit roles and flags rather than profile data.
Removing all claims
Saving an empty editor is refused on purpose, to avoid wiping claims by
accident. To remove everything, click Clear all claims. It saves an empty
claims object ({}), and the message reads Saved — all custom claims
cleared. Loading a user whose claims are {} shows an empty editor and Loaded
— user has no custom claims set.
When changes take effect
Tokens already issued keep the old claims until they expire, about an hour. A
client can pick up the change sooner by force-refreshing its token, for
example getIdToken(true) in the Firebase JavaScript SDK. New tokens from
UID -> ID Token include the change immediately.
App Check
Exchanges an App Check debug token for a real App Check token. Use it to call App Check–protected backends, such as Cloud Functions or your own server, from scripts or API tools that cannot run the App Check SDK.
Needs: the Project ID, and an app with an App ID and API key. No service account is required. It works for web, Android and iOS apps; the tab names the app it will use.
Before you start
Register a debug token for the app in the Firebase console under App Check → Apps → ⋮ → Manage debug tokens. The steps are in Preparing your Firebase project.
Exchanging a debug token
- Paste the debug token into Debug token. The field is masked.
- Click Exchange.
The result shows the App Check Token with a Copy button, its TTL
(time to live, set by the app’s App Check settings), and the decoded claims.
Send the token in the X-Firebase-AppCheck header:
curl -H "X-Firebase-AppCheck: $APP_CHECK_TOKEN" https://us-central1-<project>.cloudfunctions.net/hello
Checking the audience
The tab ends with a reminder to compare your server’s project with the aud
claim. App Check tokens list the project by number and by ID, for example
["projects/316659987175","projects/fir-token-toolkit-demo"]. A backend
configured for a different project rejects the token.
Saving the debug token
Each app keeps its own debug token, so switching apps switches tokens too. Like API keys, debug tokens are saved between launches only when remember API keys & debug tokens is ticked. Otherwise you paste it again after restarting.
When the exchange fails
API error (403): App attestation failed. almost always means the debug token
is not registered for this App ID. See
Troubleshooting for other errors.
Reading the results
Every tab presents tokens the same way.
The token box
The token appears in a read-only, scrollable box with its name (Custom Token, ID Token or App Check Token) and a Copy button above it. Copy puts the whole token on the clipboard. If the clipboard is unavailable, the error appears next to the button; you can still select the text in the box and copy it with Ctrl+C (⌘+C on macOS).
Decoded JWT claims
Click Decoded JWT claims to expand a table of everything in the token’s payload, sorted by name. Values are selectable for copying. Scroll the tab to reach the rest of a long table.
Timestamps are shown twice, as the raw Unix time and as UTC, for example
1791308692 (2026-10-06T17:44:52+00:00). That applies to iat (issued at),
exp (expires) and auth_time (when the user signed in). Nested values, like
the firebase claim in an ID token, are shown as compact JSON.
The table decodes the payload without checking the signature. It tells you what a token says, not whether a server will accept it.
Errors
When a request fails, the tab shows Error: and the message in red under its
button. Problems with the top bar or the service account show in the status
bar under the tabs instead, with a dismiss button. See
Troubleshooting for what the messages mean.
Settings and storage
Everything the app remembers lives in one file, app.ron:
| Platform | Location |
|---|---|
| Linux | ~/.local/share/firebase-token-toolkit/app.ron |
| macOS | ~/Library/Application Support/firebase-token-toolkit/app.ron |
| Windows | %APPDATA%\firebase-token-toolkit\data\app.ron |
The file is written when the app closes and periodically while it runs.
What is saved
| Value | Saved | Notes |
|---|---|---|
| Profile names | Always | |
| Service-account path | Always | The path only. The key file itself is never copied. |
| Project ID | Always | |
| Active profile, last-used tab | Always | |
| Window size and position | Always | |
| Each app’s name, type and App ID | Always | |
| Android package and SHA-1, iOS bundle ID | Always | Public identifiers, included in every app build |
| Selected app | Always | |
| Each app’s API key | Only with remember API keys & debug tokens | Plaintext |
| Each app’s App Check debug token | Only with remember API keys & debug tokens | Plaintext |
| Service-account private key | Never | Re-read from the saved path at every launch |
| OAuth access tokens | Never | Held in memory, refreshed automatically |
| Minted custom, ID and App Check tokens | Never |
The remember API keys & debug tokens checkbox applies to every profile at once. When it is off, the app clears every app’s API key and debug token, in all profiles, when it starts and again every time it saves, so unticking it also removes values that were saved earlier. Clicking Load apps fetches the keys again.
If the service-account file has moved or been deleted, the app starts with no
service account loaded and the status indicator shows ● not configured. Use
Browse… to point the profile at the new location.
Resetting the app
Close the app and delete app.ron. The next launch starts with a single empty
profile named Default.
Upgrading from 0.1.x
Version 0.1.x stored one API key, App ID and debug token per profile. The first launch of a newer version turns them into the profile’s first app, named after its type (for example Web app) and with its type taken from the App ID. If remember was off, 0.1.x saved none of the three, so the profile starts with one empty web app; Load apps fills in the project’s apps.
Upgrading from early versions
Versions before profiles existed stored a single set of fields at the top level
of the file. The first launch of a newer version moves those values into a
profile named Default; nothing needs to be done by hand.
Google endpoints
Every network request the app makes goes to a Google endpoint over TLS, and only when you click a button that needs it. There is no telemetry, analytics or update check. Opening a link from the About dialog hands that URL to your browser; nothing else leaves the app.
| Action in the app | Request | Authenticated with |
|---|---|---|
| Generate on UID -> Custom Token | None. The token is signed locally. | — |
| Generate on UID -> ID Token, Exchange on Custom -> ID Token | POST identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken | Selected app’s API key |
| Load users / Refresh | GET identitytoolkit.googleapis.com/v1/projects/{project}/accounts:batchGet (1,000 per page, up to 5,000) | OAuth access token |
| Lookup, Load current | POST identitytoolkit.googleapis.com/v1/projects/{project}/accounts:lookup | OAuth access token |
| Save, Clear all claims | POST identitytoolkit.googleapis.com/v1/projects/{project}/accounts:update, then a lookup to read the result back | OAuth access token |
| Exchange on App Check | POST firebaseappcheck.googleapis.com/v1beta/projects/{project}/apps/{app}:exchangeDebugToken | Selected app’s API key |
| Load apps | GET firebase.googleapis.com/v1beta1/projects/{project}:searchApps, then for each app GET …/webApps/{app}/config, …/androidApps/{app}/config and …/androidApps/{app}/sha, or …/iosApps/{app}/config | OAuth access token |
API keys and app identity
Requests authenticated with an API key send it as the key query parameter.
For an Android or iOS app, the request also names the app, so that keys
restricted to that app are accepted:
| App type | Headers |
|---|---|
| Web | none |
| Android | X-Android-Package: <package>, X-Android-Cert: <SHA-1, 40 uppercase hex digits> |
| iOS | X-Ios-Bundle-Identifier: <bundle ID> |
A header is left out when its value is empty, and a SHA-1 that is not 40 hex digits is not sent.
OAuth access tokens
Calls marked OAuth access token first exchange a JWT, signed with the
service-account key, at oauth2.googleapis.com/token. The token is requested
with two scopes:
https://www.googleapis.com/auth/identitytoolkithttps://www.googleapis.com/auth/cloud-platform
It is cached in memory and refreshed when it is within about a minute of expiring. Switching profiles discards it.
cloud-platform is a broad scope. What the token can actually do is limited by
the IAM roles granted to the service account, which is one more reason to use
a development project’s key. See Security.
Troubleshooting
Messages appear in one of three places: the status bar under the tabs, the
Users panel, or in red as Error: … under a tab’s button. Error text from
Google is passed through unchanged, so search the message itself if it is not
listed here.
The app does not open
If no graphics backend works, the app shows a dialog that begins “Firebase Token Toolkit could not start.” The app tries OpenGL first and then wgpu, which can fall back to a software renderer, so this almost always means a virtual machine or remote desktop session with no usable graphics at all. Enabling 3D acceleration for the VM usually fixes it.
On Linux, launching from a terminal shows the detail. A line like
OpenGL backend unavailable (…); retrying with wgpu is informational; the app
carries on with wgpu.
Service account
| Message | Cause and fix |
|---|---|
Could not load service account: read service account file: <path> | The file cannot be read. Check that it exists and that your user can read it. |
Could not load service account: parse service account JSON: <path> | The file is not valid JSON, or not a service-account key. Download a fresh key. |
Could not load service account: service account JSON must contain client_email and private_key | The JSON is some other kind of credential, such as a web app config. Use Generate new private key in the Firebase console. |
parse RSA private key (must be PEM PKCS#8 / PKCS#1) | The private_key field has been edited or mangled. Download a fresh key. |
The file picker closed unexpectedly | On Linux, the XDG desktop portal is missing or crashed. Install xdg-desktop-portal-gtk or your desktop’s portal. |
● not configured after a restart | The saved key file has moved. Use Browse… to find it again. |
Authentication with Google
| Message | Cause and fix |
|---|---|
oauth2 token error (400): … invalid_grant … | The key has been deleted or disabled in Google Cloud, or your system clock is off by several minutes. Check the clock, then generate a new key. |
oauth2 token error (401): … | The service account no longer exists. Generate a key for an active account. |
POST oauth2 token (no further detail) | The request never reached Google. Check your network connection and any proxy. |
Users panel
| Message | Cause and fix |
|---|---|
Load a service account and set Project ID to browse users. | Load a key and make sure Project ID is filled in. |
No user found for that query | Lookup needs the whole value: a complete email (case does not matter), a phone number in full + country-code form, or an exact UID. To match part of a value, type into the search box without pressing Enter; that filters the users already loaded. |
API error (403): … | The service account lacks permission to read users, or the Identity Toolkit API is disabled for the project. |
GET accounts:batchGet | The request never reached Google. Check your connection. |
Tokens
| Message | Cause and fix |
|---|---|
API key required | The selected app has no API key. Click Load apps, or paste the key. |
API error (400): INVALID_CUSTOM_TOKEN : Invalid assertion format. 3 dot separated segments required. | The pasted text is not a whole token. It usually lost characters while being copied. |
API error (400): INVALID_CUSTOM_TOKEN … with other text | The token has expired (custom tokens last one hour) or is malformed. Generate a new one. |
API error (400): CREDENTIAL_MISMATCH | The custom token was signed with a key from a different project than the API key belongs to. Check that the profile’s key and API key are from the same project. |
API error (400): API key not valid. Please pass a valid API key. | The API key is wrong or deleted. Copy it again from Project settings → General. |
API error (403): Requests from this Android client application <empty> are blocked. (or the iOS equivalent) | The key is restricted to an Android or iOS app, but the selected app is a different type, or its Package or Bundle ID is empty. Select the matching app, or fill in its identifiers. |
API error (403): Requests from this Android client application dev.example.app are blocked. | The package or SHA-1 does not match the key’s restriction. Check both against the key in Google Cloud Console → Credentials. A SHA-1 marked invalid in the top bar is not sent. |
API error (403): Requests from this iOS client application <bundle> are blocked. | The bundle ID does not match the key’s restriction. |
API error (403): … are blocked. (other) | The API key has restrictions that exclude the Identity Toolkit API or the App Check API. Loosen them in Google Cloud Console. |
Invalid claims JSON: … / custom claims must be a JSON object | The optional claims box must hold a JSON object, such as {"role":"admin"}, or be empty. |
Paste a custom token first | The Custom -> ID Token input is empty. |
User Custom Claims
| Message | Cause and fix |
|---|---|
Serialized claims are N bytes; Firebase limit is 1000 bytes. | Firebase rejects claims over 1,000 bytes. Shorten keys or move data to your database. |
Claims editor is empty (use 'Clear all claims' to wipe). | Save with an empty editor is refused on purpose. To remove every claim, use Clear all claims. |
Claims must be a JSON object. | Top-level arrays, strings and numbers are not allowed. |
User not found | The user was deleted after you picked them. Refresh the Users panel. |
App Check
| Message | Cause and fix |
|---|---|
Debug token required | Paste the debug token into the tab. |
API error (403): App attestation failed. | The debug token is not registered for this App ID. Check it under App Check → Manage debug tokens, and that it was registered for the same app as the App ID in the top bar. |
API error (403): … with other text | The App Check API is not enabled for the project, or the API key’s restrictions exclude it. |
API error (404): … | The App ID does not exist in the project named in Project ID. |
Load apps
| Message | Cause and fix |
|---|---|
Loading apps failed: API error (403): … | The service account may not read Firebase apps, or the Firebase Management API is disabled. Grant Firebase Viewer (roles/firebase.viewer), or add the app by hand with + Add app. |
No apps found in this Firebase project. | The project has no registered apps. Add one under Project settings → General → Your apps. |
Loaded apps: … Some details are missing — … | The list loaded, but one app’s config or certificates could not be read. That app is listed without its key; fill it in by hand. |
Linux desktop issues
Browse… does nothing. The file chooser goes through an XDG desktop
portal. Install xdg-desktop-portal-gtk (or your desktop’s portal) and log in
again.
Copy shows copy failed: …. The clipboard needs X11 or XWayland. Under a
pure Wayland session without XWayland, select the token text and copy it with
Ctrl+C instead.
About dialog links return 404
The Changelog and Security notes links in the About dialog point at the git tag matching the app’s version. A build from an untagged commit has no such tag, so the links 404. Release builds are not affected.
FAQ
Short answers to the questions that usually bring people here, with links to the full pages.
How do I get a Firebase ID token to test my API in Postman or curl?
Load your service-account key, click Load apps, pick a user in the Users panel, and click Generate on the UID -> ID Token tab. Click Copy and send the token as a bearer token:
curl -H "Authorization: Bearer <ID token>" https://localhost:8080/api/me
In Postman, choose Authorization → Bearer Token and paste it. The Quick start walks through it with screenshots.
How do I test a backend that verifies Firebase ID tokens?
The tokens the app produces are real ID tokens issued by Google for your
project, so they pass verifyIdToken in the Firebase Admin SDKs and any other
check against Google’s public keys. Generate one for the user you want to test
as, and call your API with it. Add claims for a single token in the Custom
claims box to test role checks without touching the user record.
How do I sign in as a specific user without their password?
The app signs a custom token for the user’s UID with your service-account key
and exchanges it with Google, the same flow as createCustomToken followed by
signInWithCustomToken. You never need the user’s password, which is also why
the service-account key must be kept safe. See Security.
How do I set Firebase custom claims without writing code?
Use the User Custom Claims tab. Load current
shows the user’s claims as JSON, you edit them, and Save writes them to the
user record, the same as setCustomUserClaims in the Admin SDK. A counter keeps
you under Firebase’s 1,000-byte limit.
Why don’t my new custom claims show up in the ID token?
Tokens issued before the change keep the old claims until they expire, about an
hour later. Generate a new token on the UID -> ID Token tab, or force a
refresh on the client, for example getIdToken(true). See
When changes take effect.
How do I get a Firebase App Check token for testing?
Register a debug token for your app under App Check → Manage debug
tokens in the Firebase console, then paste it into the
App Check tab and click Exchange. Send the result in
the X-Firebase-AppCheck header. This works for web, Android and iOS apps.
Can I use an API key restricted to an Android or iOS app?
Yes. Select the Android or iOS app in the top bar and the app sends its package name and SHA-1, or bundle ID, with each request, as the Firebase SDKs do. See API keys restricted to an app.
How do I decode a Firebase JWT?
Every token the app shows has a Decoded JWT claims table underneath, with
iat, exp and auth_time converted to readable UTC times. The table shows
what a token says; it does not verify the signature. See
Reading the results.
How long do the tokens last?
Custom tokens and ID tokens last one hour. An App Check token lasts for the time to live set in your App Check settings, shown as TTL under the token. When a token expires, generate a new one.
Does it work with the Firebase Auth emulator?
No. The app talks to Google’s production endpoints for your project, so it needs a real Firebase project. Use a development project rather than production; see Preparing your Firebase project.
Which platforms does it run on?
Linux (x86_64, glibc 2.35 or newer), Windows (x86_64) and macOS 11 or newer on Apple Silicon or Intel. See Installation.
Is it free?
Yes. It is open source under the MIT licence, and its source is on GitHub.
Security
Firebase Token Toolkit loads a service-account private key and mints real,
signed tokens with it. The app is about as sensitive as the key file itself.
SECURITY.md
in the repository is the authoritative description; this page is the short
version for day-to-day use.
What the app keeps
- The private key is read from the file you choose and held in memory. It is never copied into the settings file.
- OAuth access tokens and every token shown on screen live in memory only.
- API keys and App Check debug tokens are written to disk, in plaintext, only when remember API keys & debug tokens is ticked. App IDs, package names, SHA-1s and bundle IDs are always saved; they are public, since every build of your app contains them. See Settings and storage.
Habits worth keeping
- Point the app at a development project. A service-account key can mint a token for any user in its project.
- Treat the window like a terminal full of secrets. An ID token on screen signs in as that user until it expires, usually an hour later. Be careful when screen sharing.
- The Users panel shows personal data. Emails, phone numbers and display names come straight from Firebase Authentication.
- Custom claims are permanent until changed. Save on the User Custom Claims tab writes to the user record, not to one token, so every future ID token for that user carries the claims.
Reporting a vulnerability
Use GitHub’s private vulnerability reporting rather than opening a public issue.
Building and contributing
Build from source
You need Rust 1.90 or newer.
git clone https://github.com/smaranjit/firebase-token-toolkit
cd firebase-token-toolkit
cargo build --release
# the binary is target/release/firebase-token-toolkit
Linux also needs the windowing and clipboard development headers:
sudo apt install -y \
libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libgl1-mesa-dev \
libx11-dev libxrandr-dev libxi-dev libxcursor-dev \
libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev pkg-config
Windows needs the MSVC toolchain from Visual Studio Build Tools, and macOS
needs the Xcode command line tools. TLS goes through rustls, so no platform
needs OpenSSL.
Contributing
Issues and pull requests are welcome.
CONTRIBUTING.md
covers the checks CI runs, the project layout, building release archives and
cutting a release.
Editing this guide
The guide is an mdBook in the docs/
folder of the repository. To preview changes locally:
mdbook serve docs --open
Every page has an edit icon in the top-right corner that opens it on GitHub.
Changes merged to main are published automatically.