Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Web, Android and iOS apps per profile.** A profile now holds a list of the
project's apps instead of a single API key and App ID, and an **App**
dropdown in the top bar picks the one the ID-token and App Check tabs use.
**Load apps** imports every app in the project, with its API key, package
name and SHA-1 or bundle ID, through the Firebase Management API; apps can
also be added by hand.
- **API keys restricted to an Android or iOS app now work.** Requests made with
such an app's key send `X-Android-Package` and `X-Android-Cert`, or
`X-Ios-Bundle-Identifier`, the way the Firebase SDKs do. Previously only the
key itself was sent, so Google rejected any key with an app restriction.

- **A user guide**, published at
<https://smaranjit.github.io/firebase-token-toolkit/>. It walks through
setting up a Firebase project, every tab, profiles and the Users panel, and
lists the error messages the app can show with what each one means. The
screenshots come from a real demo project, and the README now opens with a
short recording of the main flow.

### Changed

- The persistence checkbox is now **remember API keys & debug tokens**, and it
no longer covers App IDs. App names, App IDs, package names, SHA-1s and
bundle IDs are always saved: they are public, and an app list that forgot
them on every restart would be of little use. API keys and App Check debug
tokens remain opt-in.
- Existing settings migrate on first launch: each profile's API key, App ID and
debug token become its first app, with the type taken from the App ID.

### Fixed

- **Clear all claims failed** with `INVALID_CLAIMS : Not a JSON Object: null`.
It sent an empty `customAttributes` string, which the Identity Toolkit API now
rejects; it now sends `{}`, as the Admin SDKs do.
- The last rows of a long **Decoded JWT claims** table could fall below the
window with no way to scroll to them. Each tab now scrolls as a whole.
- A refused **Save** on the User Custom Claims tab (for example, over the
1,000-byte limit) no longer shows the previous save's "Saved." message next to
the error.
- `README.md` and `SECURITY.md` called the persistence checkbox "Remember
secrets"; its label is **remember API key & App ID**. They also left out that
the App Check debug token is gated by the same checkbox, and `SECURITY.md`
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ arboard = "3"
# private key needs to be committed to the repository.
rsa = { version = "0.9", features = ["pem"] }
rand = "0.8"
# Parses saved-settings fixtures in the config migration tests. Already in the
# tree through eframe's persistence feature; keep the version in step with it.
ron = "0.11"

[build-dependencies]
winresource = "0.1"
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A native desktop GUI for the Firebase auth tokens you need during development an
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Docs](https://img.shields.io/badge/docs-user%20guide-blue.svg)](https://smaranjit.github.io/firebase-token-toolkit/)

![Loading users, picking one, and generating an ID token](docs/src/images/quick-start.gif)
![Loading the project's apps, picking the Android app and a user, and generating an ID token](docs/src/images/quick-start.gif)

**[Read the user guide →](https://smaranjit.github.io/firebase-token-toolkit/)** It covers setup, every tab, profiles, and troubleshooting.

Expand Down Expand Up @@ -41,25 +41,26 @@ xattr -dr com.apple.quarantine "/Applications/Firebase Token Toolkit.app"

## Features

- **Multiple profiles** — switch between Firebase projects (dev/staging/prod, multiple clients, …) from a dropdown in the top bar. Each profile remembers its own service-account path, project ID, API key, App ID, and debug token. Switching a profile reloads the service account and clears the user list, OAuth cache, selected UID, and all tab outputs.
- **Multiple profiles** — switch between Firebase projects (dev/staging/prod, multiple clients, …) from a dropdown in the top bar. Each profile remembers its own service-account path, project ID, and apps. Switching a profile reloads the service account and clears the user list, OAuth cache, selected UID, and all tab outputs.
- **Web, Android and iOS apps** — **Load apps** imports the project's apps with their API keys through the Firebase Management API, or add them by hand. Requests made with an Android or iOS app's key send its package name and SHA-1, or bundle ID, so keys restricted to that app work.
- **UID -> Custom Token** — sign a Firebase custom token (RS256) using a service account, with optional custom claims.
- **UID -> ID Token** — one-shot: sign a custom token, then exchange it via `signInWithCustomToken`.
- **Custom -> ID Token** — paste a custom token and exchange it for an ID token.
- **User Custom Claims** — set persistent `customAttributes` on a user record via `accounts:update`. Loads the user's existing claims, edits them as JSON, validates the 1000-byte limit, and supports a "clear all" action. Future ID tokens automatically include the saved claims.
- **App Check** — exchange a registered debug token via `firebaseappcheck.googleapis.com`.
- **Dynamic UID picker** — a left-side panel lists every user via `accounts:batchGet` (paginated up to 5,000) and lets you search by email, phone, or UID. Direct lookups fall through to `accounts:lookup` for users not present in the loaded page.
- Decoded JWT claims table for every token, with timestamp → ISO conversion and clipboard copy.
- Persists project ID, service-account path, and last-used tab between launches. API keys, App IDs and App Check debug tokens are persisted **only if you opt in** — see [Security](#security).
- Persists project ID, service-account path, and last-used tab between launches. API keys and App Check debug tokens are persisted **only if you opt in** — see [Security](#security).

## Configure

In the top bar:

1. **Service account** → *Browse* and pick the JSON from Firebase Console → Project Settings → Service accounts → *Generate new private key*. The project ID auto-populates from the file.
2. **API key** → Firebase Console → Project Settings → General → Web API Key. Only needed for the ID-token and App Check tabs.
3. **App ID** → only needed for the App Check tab.
2. **Load apps** → imports the project's web, Android and iOS apps with their App IDs and API keys. Or use **+ Add app** and paste an API key and App ID from Firebase Console → Project Settings → General → Your apps.
3. Pick the app to use from the **App** dropdown. Its API key is used by the ID-token and App Check tabs.

The status indicator next to the service account turns green and reads **ready** once the service account is loaded and an API key is set. The [quick start](https://smaranjit.github.io/firebase-token-toolkit/quick-start.html) walks through it with screenshots.
The status indicator next to the service account turns green and reads **ready** once the service account is loaded and the selected app has an API key. The [quick start](https://smaranjit.github.io/firebase-token-toolkit/quick-start.html) walks through it with screenshots.

## Build from source

Expand Down Expand Up @@ -89,7 +90,7 @@ To reproduce the packaged release artifacts, see [`scripts/`](https://github.com

This tool handles Firebase **service-account private keys** and mints signed tokens with them. Please read [SECURITY.md](https://github.com/smaranjit/firebase-token-toolkit/blob/main/SECURITY.md) before using it against anything that matters — it covers what is written to disk, what stays in memory, and how to report a vulnerability.

The short version: the service account is read from the path you pick and never copied; OAuth access tokens live in memory only; API keys, App IDs and App Check debug tokens are written to disk only when you tick **remember API key & App ID**.
The short version: the service account is read from the path you pick and never copied; OAuth access tokens live in memory only; API keys and App Check debug tokens are written to disk only when you tick **remember API keys & debug tokens**.

## Contributing

Expand Down
12 changes: 7 additions & 5 deletions README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ SETTING IT UP
Project Settings > Service accounts > Generate new private key. The
project ID fills itself in from the file.

2. API key. Firebase Console > Project Settings > General > Web API Key.
Only needed for the ID token and App Check tabs.
2. Apps. Click Load apps to import the project's web, Android and iOS
apps with their API keys. Or click + Add app and paste an API key and
App ID from Firebase Console > Project Settings > General > Your apps.

3. App ID. Only needed for the App Check tab.
3. Pick the app to use from the App list. Its API key is used by the
ID token and App Check tabs.

The indicator in the top right turns green once the service account is
loaded and an API key is set.
The indicator turns green once the service account is loaded and the
selected app has an API key.


IF NOTHING OPENS
Expand Down
15 changes: 12 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ Persisted for every profile:
on demand and never copied into the config.
- Project ID, and the last-used tab.
- Window size and position.
- Each app's name, type, App ID, Android package name and SHA-1, and iOS bundle
ID. These are public identifiers that ship inside every build of the app.

Persisted **only when "remember API key & App ID" is enabled**:
Persisted **only when "remember API keys & debug tokens" is enabled**:

- Web API key, App ID, and App Check debug token.
- Each app's API key and App Check debug token.

These are stored in **plaintext**. There is no OS keychain integration — the file is
protected by nothing but its filesystem permissions. If you would not paste the value
into a plaintext file, leave "remember API key & App ID" off. Turning it off clears those fields
into a plaintext file, leave "remember API keys & debug tokens" off. Turning it off clears those fields
for *all* profiles on the next save (`clear_secrets_all` in `src/config.rs`).

**Never persisted:** the service-account private key, OAuth access tokens, and any
Expand All @@ -59,6 +61,13 @@ All requests go to Google endpoints over TLS (`rustls` — no OpenSSL):
- `identitytoolkit.googleapis.com` — `signInWithCustomToken`, `accounts:batchGet`,
`accounts:lookup`, `accounts:update`
- `firebaseappcheck.googleapis.com` — `exchangeDebugToken`
- `firebase.googleapis.com` — `searchApps` and each app's config and SHA
certificates, when you click **Load apps**. The responses include the apps'
API keys, which are held like any other API key above.

Requests made with an Android or iOS app's API key carry that app's package
name and SHA-1, or bundle ID, in the `X-Android-Package`, `X-Android-Cert` and
`X-Ios-Bundle-Identifier` headers, as the Firebase SDKs do.

There is **no telemetry, analytics, or update check**. The application makes no
network request you did not initiate.
Expand Down
58 changes: 42 additions & 16 deletions docs/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Everything the tabs need comes from the top bar. Its values belong to the
active [profile](profiles.md), so switching profiles swaps them all at once.
The first rows hold the profile and service account; the last two hold the
selected [app](#apps).

![The top bar with a service account loaded and the indicator reading ready](images/top-bar-ready.png)

Expand All @@ -26,32 +28,56 @@ right now:

| Indicator | Meaning | Tabs available |
|---|---|---|
| **not configured** (red) | No service account loaded | *Custom -> ID Token* and *App Check* only, given an API key |
| **SA only** (yellow) | Service account loaded, no API key | *UID -> Custom Token*, *User Custom Claims*, the Users panel |
| **ready** (green) | Service account and API key | All tabs (*App Check* also needs an App ID) |
| **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.

## API key
## remember API keys & debug tokens

The project's Web API key. The ID-token tabs and App Check send it to Google
with each request. The field is masked.

## App ID

The web app's App ID, used only by the App Check tab.

## remember API key & App ID

Off by default. When ticked, the API key, App ID 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. See
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](reference/settings-storage.md).

## 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.

![The app rows for an Android app, showing its package name and SHA-1](images/top-bar-android.png)

| 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](firebase-setup.md#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](guide/users-panel.md), a **Selected
Expand Down
67 changes: 51 additions & 16 deletions docs/src/firebase-setup.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Preparing your Firebase project

The app needs up to three things from your Firebase project. Which ones depend
on the tabs you plan to use:
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 | Web API key | App ID |
| 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 | ✔ | |
Expand Down Expand Up @@ -39,29 +40,63 @@ happens locally with the key and needs no extra role.
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**.

## Web API key

Under **Project settings** → **General**, copy **Web API key**. If your project
has no web app yet, add one from the same page first.

If you have restricted the key in Google Cloud Console, allow at least the
## Apps: web, Android and iOS

Each [profile](profiles.md) 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](configuration.md#apps).

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 ID (App Check only)

Under **Project settings** → **General** → **Your apps**, copy the **App ID** of
the web app, which looks like `1:316659987175:web:ebcef8864368ab1a12a842`.

## App Check debug token (App Check only)

The App Check tab exchanges a *debug token* you have registered for the app:
The App Check tab exchanges a *debug token* you have registered for the app.
This works the same for web, Android and iOS apps:

1. Register the app with App Check under **App Check** → **Apps** if you have
not already.
2. Open the app's overflow menu (⋮) and choose **Manage debug tokens**.
3. Click **Add debug token**, give it a name, and either generate a token or
paste one your client printed. Copy the value; it is a UUID.
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.
Loading
Loading