@bayonai/firebase-auth
v0.5.0
Published
Reusable Firebase authentication actions, React provider, MUI login surfaces, and passwordless email Functions.
Maintainers
Readme
@bayonai/firebase-auth
Reusable Firebase authentication primitives for BayonAI apps.
The package includes app-agnostic Firebase auth actions, a React auth provider, configurable MUI login surfaces, and Firebase Functions factories for server-owned registration and passwordless email code/link login. Each app still owns Firebase initialization, workspace bootstrap, routes, copy, theme, Firestore domain logic, and the email transport used by Functions.
Successfully completing a passwordless code or link marks an existing account's
email verified: accepting an emailed credential proves control of its recipient
address. Host apps that treat other emailed links as verification must attach
their own recipient-bound proof in the email adapter and complete that proof
before opening the original action. This ownership proof can be reusable while
password reset and sign-in action credentials retain their separate security
limits. Failed registration email delivery keeps the account and reports
verificationEmailSent: false for a later resend.
Simplified Authentication UX
All package implementations should follow this contract:
Create account
-> server creates the Firebase user
-> server creates a custom token and attempts the branded verification email
-> server returns the token and delivery status, preserving the account if mail fails
-> client signs in immediately
-> app is usable while email verification remains a soft account state
Returning password user
-> password sign-in
-> client enters the app whether or not emailVerified is true
Email action completion
-> server proves mailbox control
-> server marks the email verified when appropriate
-> client signs in automatically when the action returns a custom tokenRegistration must not create a Firebase user in the browser and then depend on
a second request to deliver verification. requireVerifiedEmail should remain
false for this flow. Verification is an ownership signal for account state,
not a forced second login. Hosts may change the copy, routes, and bootstrap
destination, but should not change these sequencing or recovery guarantees.
Install
pnpm add @bayonai/firebase-authPeer dependencies are intentionally external so the host app controls runtime versions:
pnpm add firebase react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styledApps that use the passwordless Functions factories also need compatible firebase-admin and firebase-functions versions in their Functions package.
Configure Login Methods
Use one method config as the source of truth for actions, provider setup, UI, and Functions options.
import {
type FirebaseAuthUiConfig,
buildFirebaseAuthMethodConfig,
} from "@bayonai/firebase-auth";
export const authMethods = buildFirebaseAuthMethodConfig({
google: {
enabled: true,
preferRedirect: "ios-standalone",
},
emailPassword: {
enabled: true,
allowAccountCreation: true,
requireVerifiedEmail: false,
sendVerificationEmail: true,
},
passwordlessEmail: {
enabled: false,
deliveryModes: ["code", "link"],
resendEnabled: true,
},
});
export const authUiConfig: FirebaseAuthUiConfig = {
appName: "Example App",
introTitle: "Sign in",
introDescription: "Use your account to continue.",
redirectAfterAuthHref: "/tasks",
disabledMethodsFallback: "Sign-in is temporarily unavailable.",
methods: authMethods,
};Defaults from buildFirebaseAuthMethodConfig:
| Method | Default |
| ------------------------------------ | ------------------ |
| google.enabled | true |
| google.preferRedirect | "ios-standalone" |
| emailPassword.enabled | true |
| emailPassword.allowAccountCreation | true |
| emailPassword.requireVerifiedEmail | false |
| passwordlessEmail.enabled | false |
| passwordlessEmail.deliveryModes | ["code", "link"] |
| passwordlessEmail.resendEnabled | true |
Disabled methods are hidden by the MUI components. If an app calls a disabled action directly, it rejects with AuthMethodDisabledError.
Client Actions
Create actions in app code so the package can use the app's Firebase instance, callable transport, storage, and function names.
import { getAuth } from "firebase/auth";
import { getFunctions, httpsCallable } from "firebase/functions";
import { createFirebaseAuthActions } from "@bayonai/firebase-auth";
import { authMethods } from "./authConfig";
const functions = getFunctions();
export const authActions = createFirebaseAuthActions({
appName: "Example App",
getAuth,
methods: authMethods,
storage: typeof window === "undefined" ? undefined : window.localStorage,
storageKey: "example.firebaseAuth.emailLoginAttempt",
functionNames: {
sendEmailLoginCode: "sendEmailLoginCode",
completeEmailLoginCode: "completeEmailLoginCode",
},
serverFunctionNames: {
registerEmailPassword: "registerEmailPassword",
},
callFunction: async (name, payload) => {
const callable = httpsCallable(functions, name);
const result = await callable(payload);
return result.data;
},
});Actions include Google popup/redirect fallback, server-owned email/password registration, email/password sign-in, passwordless code/link completion, sign-out, and shared auth error formatting. A configured registerEmailPassword server function is the package path for account creation; the client-created fallback remains only for hosts that deliberately omit that server function and do not send verification from the package.
User-facing error contract
Authentication UI must display only package-owned recovery guidance. Firebase,
identity-provider, callable, and SDK exception messages are diagnostic data and
must be logged or retained as an error cause, never rendered directly. The
shared formatGoogleAuthErrorMessage, formatEmailLoginErrorMessage, and
formatPasswordAuthErrorMessage helpers enforce this fail-closed behavior, and
the packaged MUI login panel invokes them directly for every authentication
failure. Hosts cannot replace the panel's mediator through its actions prop.
Use FirebaseAuthUserError only for deliberate, user-safe guidance produced by
application or package code. Unknown Error messages remain untrusted and are
replaced with a stable fallback.
React Provider
The provider owns Firebase auth readiness, Google redirect result consumption, optional bootstrap caching, optional claim parsing, and app bootstrap callbacks.
import {
FirebaseAuthProvider,
useFirebaseAuth,
} from "@bayonai/firebase-auth/react";
import { getAuth } from "firebase/auth";
import { authActions } from "./authActions";
import { ensureUserWorkspace } from "./ensureUserWorkspace";
const defaultClaims = { admin: false };
export function AppAuthProvider({ children }: { children: React.ReactNode }) {
return (
<FirebaseAuthProvider
actions={authActions}
defaultClaims={defaultClaims}
getAuth={getAuth}
readClaims={(claims) => ({ admin: claims.admin === true })}
bootstrapUser={async (user) => {
const workspace = await ensureUserWorkspace(user);
return { primaryEntityId: workspace.id };
}}
>
{children}
</FirebaseAuthProvider>
);
}
export function CurrentUserEmail() {
const auth = useFirebaseAuth<typeof defaultClaims>();
return auth.user?.email ?? null;
}Keep app-specific bootstrap behavior, workspace creation, authorization claims, and route decisions in the host app.
MUI Login UI
LoginView wraps LoginPanel with auth-status handling and redirect behavior. Both components render only enabled login methods.
import { LoginView } from "@bayonai/firebase-auth/mui";
import { useRouter } from "next/navigation";
import { useFirebaseAuth } from "@bayonai/firebase-auth/react";
import { authActions } from "./authActions";
import { authUiConfig } from "./authConfig";
export function LoginPage() {
const router = useRouter();
const auth = useFirebaseAuth();
return (
<LoginView
actions={authActions}
authStatus={auth.status}
config={authUiConfig}
replaceRoute={(href) => router.replace(href)}
loadingLabel="Preparing your workspace..."
labels={{
googleButton: "Continue with Google",
passwordSignInButton: "Sign in",
passwordCreateButton: "Create account",
sendCodeButton: "Send code",
verifyCodeButton: "Verify code",
resendCodeTooltip: "Send a new code",
}}
/>
);
}Use LoginPanel directly when the app already owns the surrounding layout. The panel accepts sx, headerSlot, statusMessage, signedInPreparing, passwordlessCooldownSeconds, labels, and an optional toast adapter.
Passwordless Firebase Functions
The Functions export provides factories for apps that enable passwordless email. The app supplies Firestore, Admin Auth, email sender, template IDs, callable options, app URL, and delivery modes.
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
import {
createCompleteEmailLoginCodeFunction,
createFirestoreAuthRateLimiter,
createRegisterEmailPasswordFunction,
createSendEmailLoginCodeFunction,
createSendPasswordResetEmailFunction,
createSendEmailVerificationFunction,
type AuthEmailSender,
} from "@bayonai/firebase-auth/functions";
const db = getFirestore();
const auth = getAuth();
const rateLimiter = createFirestoreAuthRateLimiter(db);
const emailSender: AuthEmailSender = {
async send(request) {
await sendEmailWithYourProvider({
to: request.to,
templateId: request.templateId,
variables: request.variables,
});
return { skipped: false };
},
};
export const registerEmailPassword = createRegisterEmailPasswordFunction(
auth,
emailSender,
{
appUrl: process.env.NEXT_PUBLIC_APP_URL,
logger: console,
onEvent: async (event) => {
await recordAuthEvent(event);
},
rateLimiter,
templateId: "email_verification",
verificationPath: "/verify-email",
},
);
export const sendEmailLoginCode = createSendEmailLoginCodeFunction(
db,
auth,
emailSender,
{
appUrl: process.env.NEXT_PUBLIC_APP_URL,
deliveryModes: ["code", "link"],
logger: console,
onEvent: async (event) => {
await recordAuthEvent(event);
},
rateLimiter,
securityLimits: {
emailLoginTtlMs: 10 * 60 * 1000,
resendCooldownMs: 5 * 60 * 1000,
maxEmailRequestsPerRecipient: 3,
maxCodeAttempts: 5,
},
signInPath: "/login",
templateId: "email_login_code",
},
);
export const completeEmailLoginCode = createCompleteEmailLoginCodeFunction(
db,
auth,
{
deliveryModes: ["code", "link"],
},
);
export const sendPasswordResetEmail = createSendPasswordResetEmailFunction(
auth,
emailSender,
{
appUrl: process.env.NEXT_PUBLIC_APP_URL,
logger: console,
onEvent: async (event) => {
await recordAuthEvent(event);
},
rateLimiter,
templateId: "password_reset",
},
);
export const sendEmailVerification = createSendEmailVerificationFunction(
auth,
emailSender,
{
appUrl: process.env.NEXT_PUBLIC_APP_URL,
logger: console,
onEvent: async (event) => {
await recordAuthEvent(event);
},
rateLimiter,
templateId: "email_verification",
verificationPath: "/verify-email",
},
);Set deliveryModes to ["code"], ["link"], or ["code", "link"] to control what gets created and sent. Do not enable passwordless login in the app config until its Functions and email adapter are deployed.
Set signInPath when an app needs emailed links to target an app-owned route
other than /login, such as a hosted mobile App Links path. The package keeps
the attempt and token query parameters stable so existing completion
handlers can continue using readEmailLoginLinkParams().
Set verificationPath when verification links should target an app-owned route
other than /login. For public verification forms, set
allowAnonymousEmailLookup: true and pass the submitted email in the callable
payload. Unknown or already verified addresses return { sent: true } so the
endpoint does not disclose account existence. Keep this option disabled when
the callable is only used by authenticated users.
Registration and password-reset delivery require a server-owned rateLimiter.
Anonymous verification requires one as well. The Bounded Functions adapters
supply the Firestore-backed limiter automatically; other hosts should reuse one
createFirestoreAuthRateLimiter(db) instance and keep its backing collection
inaccessible to browser clients. Each public email flow enforces a recipient
limit and, when Firebase exposes a requester address, a wider requester limit.
Functions Diagnostics
The Functions API exposes one app-owned auth email contract:
import type {
AuthEmailSender,
AuthPackageEvent,
AuthPackageLogger,
} from "@bayonai/firebase-auth/functions";Use AuthEmailSender for provider-specific delivery adapters. The package only
creates auth links, login attempts, hashes, and typed delivery requests. Host
apps own email providers, templates, sender identities, logs, retries, and
operator alerts.
Use logger for operational diagnostics and onEvent for app-owned audit or
metrics pipelines. Event payloads are intentionally safe: they can include event
names, attempt IDs, reasons, booleans, and email domains, but never login codes,
sign-in tokens, reset links, or full recipient emails.
securityLimits lets apps tune passwordless login defaults without forking the
package:
resendCooldownMs is the rolling per-recipient window. The package permits up
to maxEmailRequestsPerRecipient requests within that window; it is not a
single-request cooldown.
| Limit | Default |
| ------------------------------ | -------- |
| emailLoginTtlMs | 600000 |
| resendCooldownMs | 300000 |
| maxEmailRequestsPerRecipient | 3 |
| registrationWindowMs | 300000 |
| maxRegistrationAttemptsPerIp | 3 |
| maxCodeAttempts | 5 |
Package Boundary
This package owns reusable auth behavior:
- Method config validation and disabled-method guards.
- Firebase client auth actions.
- Auth state/provider boot sequence.
- Shared login UI behavior and messages.
- Server-owned registration and passwordless email callable factories and request/result types.
Host apps own app-specific behavior:
- Firebase app initialization and callable wiring.
- Workspace/user bootstrap such as
ensureUserWorkspace. - Routes, redirects, copy, theme, and page layout.
- Firestore domain models and authorization semantics.
- Email provider configuration, templates, and sender identity.
Release Checklist
From the Bounded repo root:
pnpm --filter @bayonai/firebase-auth build
pnpm --filter @bayonai/firebase-auth test
pnpm --filter @bayonai/firebase-auth lint
pnpm --filter @bayonai/firebase-auth pack:smoke
pnpm --filter @bayonai/firebase-auth consumer:smoke
pnpm lint
pnpm build
pnpm test
git diff --checkconsumer:smoke installs the packed tarball into a temporary app and checks
ESM, CommonJS, TypeScript, and subpath imports from the outside. Run it before
publishing any version.
From packages/firebase-auth:
npm publish --dry-run --access public
npm view @bayonai/firebase-auth name version dist.tarball time --json
npm whoamiBefore the first real publish, npm view should return E404 and npm whoami must succeed. If npm whoami returns E401, run npm login first.
Real publish command:
npm publish --access publicAfter publish, verify the registry entry:
npm view @bayonai/firebase-auth name version dist.tarball time --jsonConsumers should stay on local file: or workspace dependencies until the npm package has been published and verified.
License
This package is proprietary commercial software. Installing, downloading, or accessing it does not grant a free license or any implied right to use, copy, modify, publish, distribute, sublicense, sell, or otherwise exploit the package. Use requires a separate written commercial license agreement with BayonAI that expressly grants those rights.
For licensing inquiries, contact BayonAI.
Error recovery copy
Errors describe a recovery action without instructing users to contact support. Keep network, sign-in, and service-availability guidance specific to the failure.
Error-copy contract: authored failures state the failed outcome without bare retry instructions. Known throttling waits, corrective steps and alternate sign-in methods remain available. Hosts own recovery controls and sanitization of legacy server messages; original errors and stable codes remain diagnostic.
Password and login presentation contract
Passwords are passed exactly as entered, including whitespace. Email and reset-code normalization are unchanged; existing provider password policies still apply. LoginPanel accepts initialEmailMethod="password" for direct password entry and passwordResetEnabled={false} to hide recovery when the host has no reset service. Disabled methods remain unavailable; defaults remain the code tab and visible reset control. Successful password sign-in clears/conceals the field. Visibility controls respect busy state and retain input focus on pointer activation. See the repository contract docs/contracts/firebase-auth-password-input.md.
