Customer Auth + Firebase OAuth: A Practical Integration Guide
Customer Auth + Firebase OAuth: A Practical Integration Guide
💡 Audience: Developers building consumer apps (web or mobile) on top of HyperBabel — who need real per-user authentication, not a sandbox API key pasted into a login screen.
Embedding HyperBabel into a mobile app traditionally meant shipping your `hb_live_…` key inside the binary. Anyone who decompiled the APK / IPA had full access to your org. Web apps had a similar story — you either built your own backend proxy or accepted that your network tab leaked the key.
The Customer Auth feature solves this. HyperBabel's backend mints per-end-user JWTs (1h access, 30d refresh) directly from your client's Firebase ID token — no hop through your own backend required. Your client apps hold only those JWTs. The org `hb_live_…` key stays server-side, in Secret Manager / Vault / Workers Secrets — and never leaves it.
How the flow works
```
[Client App] ──Sign in with Firebase──→ Firebase ID token
│
│ POST /customer/auth/firebase-exchange
│ Authorization: Bearer <firebase-id-token>
▼
[HyperBabel API] ── Google JWKS verify → org lookup → customer JWT
│
│ { access_token, refresh_token, ... }
▼
[Client App] ──Bearer <customer access token>──→ /chat, /video, /translate
```
This is the same pattern SendBird and Stream call "session tokens", and how Twilio's "Access Tokens" work. We picked it because it leaves identity and GDPR responsibility with you, but takes the painful "where do I keep the API key" question off the table. Firebase Auth users get an extra shortcut: HyperBabel verifies the Firebase ID token directly against Google's JWKS, so you don't need to build or operate a backend exchange function for the Firebase path.
Step 1 — Set up Firebase Auth
In the Firebase Console, enable Auth → Google + Apple sign-in. Add iOS and Android apps (if you're shipping mobile) or set up the Web SDK. Download the platform config files. Firebase handles passwordless OAuth, Apple's email-relay quirks, and account recovery for you.
Already on Auth0, Cognito, Supabase Auth, or your own email/password? Skip to the "Not using Firebase?" section near the end — the same Customer Auth feature works with any auth provider, just through a slightly different endpoint.
Step 2 — Register your Firebase project in the HyperBabel Console (one-time)
Open Dashboard → Customer Auth → Firebase Projects → Add Firebase project. You'll be asked for two things:
1. Your Firebase project ID — looks like `my-app-prod` (Firebase Console → Project settings → General → "Project ID").
2. A short-lived Firebase ID token that proves you own the project. The form offers two ways to get one (~2 minutes) — pick whichever fits:
The console verifies the token against Google's JWKS, checks its `aud` claim equals the project ID you entered, then maps the project to your HyperBabel org. The token itself is discarded immediately — never stored. From this point on, any user signed in to this Firebase project can be exchanged for a HyperBabel customer JWT.
Step 3 — Call /customer/auth/firebase-exchange directly from your client
No backend, no Cloud Function. The client sends its Firebase ID token straight to HyperBabel:
React Native
```ts
import auth from '@react-native-firebase/auth';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import * as SecureStore from 'expo-secure-store';
// 1. Sign in with Google (Apple uses auth.AppleAuthProvider; same shape)
await GoogleSignin.hasPlayServices();
const { idToken: googleIdToken } = await GoogleSignin.signIn();
const credential = auth.GoogleAuthProvider.credential(googleIdToken);
await auth().signInWithCredential(credential);
// 2. Get a fresh Firebase ID token (force-refresh avoids edge-cases near expiry)
const firebaseIdToken = await auth().currentUser!.getIdToken(true);
// 3. Exchange for a HyperBabel customer JWT — direct, no backend hop
const res = await fetch(
'https://api.hyperbabel.com/api/v1/customer/auth/firebase-exchange',
{
method: 'POST',
headers: {
Authorization: `Bearer ${firebaseIdToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ preferred_lang_cd: 'en' }),
},
);
if (!res.ok) throw new Error('Token exchange failed');
const session = await res.json();
// 4. Store and use
await SecureStore.setItemAsync('hb_access', session.access_token);
await SecureStore.setItemAsync('hb_refresh', session.refresh_token);
```
Web (any framework)
```ts
import { getAuth, signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
const auth = getAuth();
await signInWithPopup(auth, new GoogleAuthProvider());
const firebaseIdToken = await auth.currentUser!.getIdToken(true);
const res = await fetch(
'https://api.hyperbabel.com/api/v1/customer/auth/firebase-exchange',
{
method: 'POST',
headers: {
Authorization: `Bearer ${firebaseIdToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ preferred_lang_cd: 'en' }),
},
);
if (!res.ok) throw new Error('Token exchange failed');
const session = await res.json();
sessionStorage.setItem('hb_access', session.access_token);
sessionStorage.setItem('hb_refresh', session.refresh_token);
```
iOS (Swift)
The pattern matches: get the Firebase ID token via `Auth.auth().currentUser?.getIDTokenForcingRefresh(true)` (or the async/await variant) → `URLSession` POST to `/customer/auth/firebase-exchange` with `Authorization: Bearer <token>` → decode the response and store `access_token` in Keychain. No SDK dependency beyond FirebaseAuth and URLSession.
Step 4 — Send the customer JWT on every HyperBabel call
Every subsequent call to `/chat`, `/video`, `/translate`, `/rtm`, etc. carries:
```
Authorization: Bearer <customer_access_token>
```
The org API key never lives on the device or in browser storage.
Step 5 — A defensive runtime guard
In your client-side HTTP client, add one line that throws if any header value ever starts with `hb_live_` or `hb_test_`:
```ts
function assertNoOrgApiKey(headers: Record<string, string>) {
for (const v of Object.values(headers)) {
if (typeof v === 'string' && (v.startsWith('hb_live_') || v.startsWith('hb_test_'))) {
throw new Error('Refusing to send a HyperBabel org API key from a client app');
}
}
}
```
Now even an accidental future commit can't leak the key.
What customer JWTs can — and cannot — do
A customer JWT can call every SDK-facing endpoint — `/chat`, `/unitedchat`, `/video`, `/translate`, `/rtm`, `/presence`, `/push`, `/storage`, `/users` — exactly as the org API key could. It cannot reach `/admin`, `/billing`, or `/auth/api-keys`. So even a leaked customer JWT can't create new keys, read invoices, or change org-level settings.
Refresh, revoke, observe
Not using Firebase? The backend-exchange pattern
If your users sign in via Auth0, Cognito, Supabase Auth, or your own email/password system, the Firebase shortcut doesn't apply — but the same Customer Auth feature still works through a small server-to-server exchange:
```ts
// Your backend (Worker / Lambda / Express) — org API key stays server-side
async function mintCustomerJwt(externalUserId: string, displayName?: string) {
const r = await fetch('https://api.hyperbabel.com/api/v1/customer/issue-token', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HYPERBABEL_API_KEY!}`, // hb_live_…
'Content-Type': 'application/json',
},
body: JSON.stringify({
external_user_id: externalUserId,
display_name: displayName ?? null,
preferred_lang_cd: 'en',
}),
});
if (!r.ok) throw new Error('Token issue failed');
return r.json(); // { access_token, refresh_token, expires_at, ... }
}
```
Authenticate the user with your auth provider on the server first, then call `/customer/issue-token`. Return the response straight to the client — refresh, revoke, and the runtime guard all work the same as in the Firebase flow.
