AuthProvider / useAuth
Live Firebase user and custom claims for the whole tree, plus app-wide defaults.
Two things in one: the live user for your component tree, and the place your auth and app-wide defaults live.
<AuthProvider auth={auth}>{children}</AuthProvider>;
const { firebaseUser, claims, isAuthenticated, isLoading } = useAuth();
if (claims?.isAdmin) showAdminNav();Every other hook works with no provider at all, given an auth of its own — it's additive, not required. useAuth is the exception: nothing else subscribes to Firebase, so without a provider it has no user to report and throws rather than claiming nobody is signed in.
useAuth
| Field | Type | What it is |
|---|---|---|
firebaseUser | User | null | The live Firebase user |
claims | Record<string, unknown> | null | Custom claims from the ID token |
isAuthenticated | boolean | Whether a user is signed in |
isLoading | boolean | True only until Firebase's first callback |
Claims update without a reload
The provider subscribes to onIdTokenChanged, not onAuthStateChanged. So firebaseUser and claims update on sign-in, sign-out, and token refreshes — meaning a custom-claim change like a role update propagates on its own.
isLoading is true only until the first callback, which is how you tell "signed out" from "not yet known".
App-wide defaults
Set these once and every hook below inherits them:
<AuthProvider
auth={auth}
onIdToken={(idToken) => createSession(idToken)}
onBeforeSignOut={() => clearSession()}
actionCodeSettings={{ url: `${origin}/auth/action`, handleCodeInApp: true }}
formatErrorMessage={(e) => formatFirebaseError(e, { messages: AUTH_ERROR_MESSAGES })}
onError={(error, { action, code }) => track('auth_error', { action, code })}
>
{children}
</AuthProvider>A hook's own option overrides the provider, and an explicit null opts that flow out entirely.
auth follows the same rule with one difference: a hook called without one uses the provider's, and passing your own overrides it — but null means "not ready yet, don't run", never "inherit". That's what lets you hold a hook back while Firebase initialises.
| Prop | What it does |
|---|---|
auth | Your Auth instance, used by every hook below that doesn't pass its own |
onIdToken | Inherited by every sign-in hook |
onBeforeSignOut | Inherited by useLogout |
actionCodeSettings | Inherited by every emailed link |
formatErrorMessage | Inherited by every hook — see Error handling |
onError | Fire-and-forget observer for every failure |
senders | Your own email sender per flow — see below |
Sending the emails yourself
Three hooks email a link, and by default each sends from the browser. senders moves them behind your own API, once, instead of per call site:
<AuthProvider
auth={auth}
senders={{
signInLink: ({ email, actionCodeSettings }) => api.sendSignInLink(email, actionCodeSettings),
passwordReset: ({ email, actionCodeSettings }) => api.sendPasswordReset(email, actionCodeSettings),
emailVerification: ({ email, actionCodeSettings }) => api.sendVerification(email, actionCodeSettings),
}}
>| Key | Replaces |
|---|---|
signInLink | useEmailLinkSignIn's sendEmail |
passwordReset | useSendPasswordResetEmail's sendEmail |
emailVerification | useSendEmailVerification's sendEmail |
One key per flow rather than a single sender, because the three send different emails — one sender for all of them could mail a password reset to someone who asked to verify an address.
Each follows the usual rule: the hook's own option wins, and null opts that one flow back to Firebase's client-side send.
// Everything through your API, except this one call
const { send } = useSendPasswordResetEmail({ sendEmail: null });Your own user record
Server-fetched user records are an app concern. Layer your own provider on top of this one rather than expecting this one to fetch them.