Use case
I'm building a backend service that needs to support both live Firebase (production) and the Auth emulator (local dev/CI), selected at startup from environment config. The service holds a long-lived auth client and uses two App capabilities:
auth() — user management (create_user, get_user, etc.)
id_token_verifier() — verify incoming ID tokens on each request
In other ecosystems (e.g. TypeScript), this is straightforward: hold a reference to a single App interface and call the same methods regardless of backend. I'd like the Rust SDK to support a similar ergonomics story without every consumer re-implementing the same adapter layer.
Current friction
App<EmulatorCredentials> and App<AccessTokenCredentials> are separate types with different method signatures:
| Method |
Live |
Emulator |
auth() |
auth() |
auth(emulator_url: String) |
id_token_verifier() |
Result<impl TokenValidator, _> |
impl TokenValidator (no Result) |
This makes it impossible to write something as simple as:
fn build_client(app: ???) -> FirebaseAuthClient { ... }
without either generics (FirebaseAuthClient<C>) or a consumer-side enum.
Workaround we had to build
We ended up with ~70 lines of boilerplate in our app just to paper over the API differences:
pub enum FirebaseAppHandle {
Live {
app: App<AccessTokenCredentials>,
project_id: String, // duplicated — App::project_id is private
},
Emulated {
app: App<EmulatorCredentials>,
emulator_url: String,
},
}
impl FirebaseAppHandle {
pub fn auth(&self) -> FirebaseAuth<ReqwestApiClient> { /* match */ }
pub fn id_token_validator(&self) -> Result<IdTokenValidator, AuthError> {
match self {
// Cannot use app.id_token_verifier() — see below
Self::Live { project_id, .. } => Ok(IdTokenValidator::Live(
LiveValidator::new_jwt_validator(project_id.clone())?,
)),
Self::Emulated { .. } => Ok(IdTokenValidator::Emulator(EmulatorValidator)),
}
}
}
pub enum IdTokenValidator {
Live(LiveValidator),
Emulator(EmulatorValidator),
}
Specific pain points
1. impl TokenValidator return type is not usable at call sites that need concrete types
App::id_token_verifier() returns impl TokenValidator, but our enum variant expects LiveValidator:
// Does not compile:
Ok(IdTokenValidator::Live(
app.id_token_verifier()?. // error: expected LiveValidator, found impl TokenValidator
))
We had to bypass App::id_token_verifier() entirely and call LiveValidator::new_jwt_validator() directly — which requires duplicating project_id because App::project_id is private.
2. Inconsistent error handling between live and emulator
Live id_token_verifier() returns Result<...>, emulator returns the validator directly. Consumers normalizing both paths need extra match arms for no functional reason.
3. Emulator URL is not part of App state
auth(emulator_url) requires the URL on every call (or the consumer must store it alongside App). For a long-lived client, the emulator host is configuration — it would be more natural to set it once at construction (e.g. from FIREBASE_AUTH_EMULATOR_HOST).
4. No shared abstraction for "live or emulated"
The phantom type parameter App<C> is a nice compile-time distinction, but most application code wants runtime selection. A first-party enum or trait would save every consumer from writing the same adapter.
Suggested improvements
Any of these would significantly improve the developer experience. Ordered roughly by impact:
Option A: First-party AppMode enum (highest impact)
pub enum App {
Live { /* ... */ },
Emulated { emulator_url: String, /* ... */ },
}
impl App {
pub async fn live() -> Result<Self, ...> { ... }
pub fn emulated(emulator_url: impl Into<String>) -> Self { ... }
pub fn project_id(&self) -> &str { ... }
pub fn auth(&self) -> FirebaseAuth<ReqwestApiClient> { ... }
pub fn id_token_verifier(&self) -> Result<IdTokenVerifier, ...> { ... }
}
pub enum IdTokenVerifier {
Live(LiveValidator),
Emulator(EmulatorValidator),
}
impl TokenValidator for IdTokenVerifier { ... }
This gives consumers a single type to store and pass around, with normalized method signatures.
Option B: Normalize the existing impl blocks
Smaller changes that would still help:
- Unify
auth(): e.g. auth(&self) -> FirebaseAuth<...> on both variants, with emulator URL set at App::emulated(url) construction time.
- Unify
id_token_verifier(): same return type on both paths — Result<IdTokenVerifier, _> or return concrete enums instead of impl TokenValidator.
- Expose
project_id() as a public accessor on both App variants.
- Return concrete types from
id_token_verifier() (LiveValidator / EmulatorValidator) rather than impl TokenValidator, or provide an SDK-owned enum that implements TokenValidator.
Option C: Object-safe TokenValidator
If TokenValidator were object-safe (e.g. via async_trait or boxed futures), consumers could use Box<dyn TokenValidator>. Currently the impl Future return on validate() prevents this, forcing the concrete-type enum workaround.
Environment
rs-firebase-admin-sdk (latest from crates.io)
- Rust 2024 edition
- Use case: Axum HTTP server holding
Arc<FirebaseAuthClient> across request handlers
Happy to contribute
If any of these directions align with your design goals, I'm happy to open a PR. Just let me know which approach you'd prefer.
Use case
I'm building a backend service that needs to support both live Firebase (production) and the Auth emulator (local dev/CI), selected at startup from environment config. The service holds a long-lived auth client and uses two
Appcapabilities:auth()— user management (create_user,get_user, etc.)id_token_verifier()— verify incoming ID tokens on each requestIn other ecosystems (e.g. TypeScript), this is straightforward: hold a reference to a single
Appinterface and call the same methods regardless of backend. I'd like the Rust SDK to support a similar ergonomics story without every consumer re-implementing the same adapter layer.Current friction
App<EmulatorCredentials>andApp<AccessTokenCredentials>are separate types with different method signatures:auth()auth()auth(emulator_url: String)id_token_verifier()Result<impl TokenValidator, _>impl TokenValidator(noResult)This makes it impossible to write something as simple as:
without either generics (
FirebaseAuthClient<C>) or a consumer-side enum.Workaround we had to build
We ended up with ~70 lines of boilerplate in our app just to paper over the API differences:
Specific pain points
1.
impl TokenValidatorreturn type is not usable at call sites that need concrete typesApp::id_token_verifier()returnsimpl TokenValidator, but our enum variant expectsLiveValidator:We had to bypass
App::id_token_verifier()entirely and callLiveValidator::new_jwt_validator()directly — which requires duplicatingproject_idbecauseApp::project_idis private.2. Inconsistent error handling between live and emulator
Live
id_token_verifier()returnsResult<...>, emulator returns the validator directly. Consumers normalizing both paths need extramatcharms for no functional reason.3. Emulator URL is not part of
Appstateauth(emulator_url)requires the URL on every call (or the consumer must store it alongsideApp). For a long-lived client, the emulator host is configuration — it would be more natural to set it once at construction (e.g. fromFIREBASE_AUTH_EMULATOR_HOST).4. No shared abstraction for "live or emulated"
The phantom type parameter
App<C>is a nice compile-time distinction, but most application code wants runtime selection. A first-party enum or trait would save every consumer from writing the same adapter.Suggested improvements
Any of these would significantly improve the developer experience. Ordered roughly by impact:
Option A: First-party
AppModeenum (highest impact)This gives consumers a single type to store and pass around, with normalized method signatures.
Option B: Normalize the existing
implblocksSmaller changes that would still help:
auth(): e.g.auth(&self) -> FirebaseAuth<...>on both variants, with emulator URL set atApp::emulated(url)construction time.id_token_verifier(): same return type on both paths —Result<IdTokenVerifier, _>or return concrete enums instead ofimpl TokenValidator.project_id()as a public accessor on bothAppvariants.id_token_verifier()(LiveValidator/EmulatorValidator) rather thanimpl TokenValidator, or provide an SDK-owned enum that implementsTokenValidator.Option C: Object-safe
TokenValidatorIf
TokenValidatorwere object-safe (e.g. viaasync_traitor boxed futures), consumers could useBox<dyn TokenValidator>. Currently theimpl Futurereturn onvalidate()prevents this, forcing the concrete-type enum workaround.Environment
rs-firebase-admin-sdk(latest from crates.io)Arc<FirebaseAuthClient>across request handlersHappy to contribute
If any of these directions align with your design goals, I'm happy to open a PR. Just let me know which approach you'd prefer.