Skip to content

Issue 29 - Custom Token Creation.#30

Open
Sydney-o9 wants to merge 1 commit intoexpl:mainfrom
Sydney-o9:feature/Issue-29-Custom-Token-Creation
Open

Issue 29 - Custom Token Creation.#30
Sydney-o9 wants to merge 1 commit intoexpl:mainfrom
Sydney-o9:feature/Issue-29-Custom-Token-Creation

Conversation

@Sydney-o9
Copy link
Copy Markdown
Contributor

@Sydney-o9 Sydney-o9 commented Mar 27, 2026

Context

All Firebase Admin SDKs have a built-in method for creating / minting custom tokens. This pull request is a suggested implementation in-par with what SDKs in other maintained languages provide. More context.

Typical Flow

Services can call either create_custom_token / create_custom_token_with_claims to generate signed JWTs which clients can then exchange for Firebase credentials using Firebase frontend SDKs.

API

create_custom_token / create_custom_token_with_claims

Mints Firebase custom tokens (RS256 JWTs) via the IAM Credentials signJwt API.

Input validation matches other Firebase Admin SDKs:

  • uid must be non-empty and ≤ 128 characters (Unicode-aware)
  • claims must be a JSON object with no reserved JWT keys (aud, exp, iat, iss, sub, etc.)

The signing service account resolves in priority order:

  1. Explicit. app.auth_with_signer("sa@project.iam.gserviceaccount.com")
  2. Auto-discovered. Fetched from the GCE metadata server when running on Cloud Run / GCE / GKE.

Comparison NodeJS SDK / Rust Implementation

Case 1. Auto-Discovered

NodeJS Admin SDK

const app = admin.initializeApp({
  credential: admin.credential.applicationDefault(),
});

const token = await admin.auth(app).createCustomToken(uid);

Rust SDK implementation

let app = App::live().await?;
let auth = app.auth();
let token = auth.create_custom_token(uid).await?;

Case 2. Explicit Signer

NodeJS Admin SDK

const app = admin.initializeApp({
  credential: admin.credential.applicationDefault(),
  serviceAccountId: "signer@project.iam.gserviceaccount.com",
});

const token = await admin.auth(app).createCustomToken(uid);

Rust SDK implementation

let app = App::live_with_project_id("project").await?;
let auth = app.auth_with_signer("signer@project.iam.gserviceaccount.com");
let token = auth.create_custom_token(uid).await?;

In a nutshell, serviceAccountId in Node.js maps to auth_with_signer() here.

Behavior in emulator

When targeting the emulator (App::emulated()), tokens are minted as unsigned JWTs (alg: none), no IAM call or service account is needed. Consistent with how other Firebase Admin SDKs handle emulator environments.

@expl any feedback, happy to help / amend / revise 👍

- [x] Implemented the suggestion made in expl#29 for the maintainer to consider.
@expl
Copy link
Copy Markdown
Owner

expl commented Mar 30, 2026

Thank you for your PR, just letting you know that I am reviewing it.

Copy link
Copy Markdown
Owner

@expl expl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for waiting/

Some bigger points before I go into small things:

  1. Please move custom token logic into separate module to keep the codebase more organized, I would also consider creating separate module for IAM signing client or possibly use https://crates.io/crates/google-cloud-iam-credentials-v1 that is generated from official schema
  2. Extracting SA email from metadata server is only one way of several, it can be also automated to extract it from external/impersonated account setup without passing this burden to the user of the crate. We already depend on google-cloud-auth crate, it comes with optional idtoken feature that provides OIDC (JWT) token for the active service account (in addition to access token required to call Firebase/IAM services) that contains the service SA email. This way is preferred because it detects best way to get the SA with least overhead. When idtoken feature is enabled IDTokenCredentials trait is implemented for Credentials struct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants