Multi-Tenancy
The GYBC platform uses Firebase Identity Platform to isolate tenants. Each tenant gets a separate user pool within a single Firebase project, with tenant-scoped JWTs and API keys enforcing boundaries at every layer.
Concepts
| Term | Description |
|---|---|
| Firebase Project | Single GCP project (gym-hero-staging) hosting all tenants |
| Firebase Tenant | Isolated user pool within the project (e.g., socayo-dashboard) |
| Platform Tenant ID | Derived from Firebase tenant by stripping the -dashboard suffix (e.g., socayo) |
Secret Key (sk_live_) | Backend-to-backend API key, scoped to a tenant |
Publishable Key (pk_live_) | Frontend-safe API key, requires a user JWT alongside it |
Architecture
Authentication Methods
1. Publishable Key + User JWT (Client Apps)
For end-user facing applications (iOS, web). Requires both a publishable key and a user JWT.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/list-threads \
-H "X-API-Key: pk_live_your_key_here" \
-H "Authorization: Bearer <firebase-user-jwt>" \
-H "Content-Type: application/json" \
-d '{}'
How it works:
- Auth service verifies the publishable key via Unkey
- Retrieves the OIDC config stored in the key's metadata
- Validates the user JWT against that OIDC config (issuer, audience, signature)
- Extracts
tenant_idfrom the key,user_idfrom the JWT - Both must be valid — the key alone or JWT alone is not enough
2. Secret Key (Backend-to-Backend)
For server-to-server calls from the tenant's own backend. No user JWT needed.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/list-threads \
-H "X-API-Key: sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{}'
3. JWT Only (Dashboard)
For tenant admins using the GYBC dashboard directly.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/list-threads \
-H "Authorization: Bearer <firebase-jwt>" \
-H "Content-Type: application/json" \
-d '{}'
Tenant Setup Guide
Step 1: Create a Firebase Tenant
In the GCP Console:
- Go to Identity Platform > Settings > Security > enable Allow tenants
- Go to Tenants > Add tenant
- Name it
<tenant-name>-dashboard(e.g.,socayo-dashboard) - Enable sign-in providers on the tenant (Google, Apple, Email/Password, etc.)
- Note the tenant ID Firebase assigns
Step 2: Create a Test User
- Select your tenant in the Identity Platform console
- Click Add user
- Create a user with email/password for testing
Step 3: Verify JWT Contains Tenant Claim
Use the Firebase Auth REST API to sign in and inspect the JWT:
FIREBASE_API_KEY="your-web-api-key" # Firebase Console > Project Settings
TENANT_ID="your-tenant-id" # e.g., socayo-dashboard-xxxxx
# Sign in
RESPONSE=$(curl -s "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${FIREBASE_API_KEY}" \
-H 'Content-Type: application/json' \
-d "{
\"email\": \"test@example.com\",
\"password\": \"your-password\",
\"returnSecureToken\": true,
\"tenantId\": \"${TENANT_ID}\"
}")
# Extract and decode the JWT
ID_TOKEN=$(echo "$RESPONSE" | jq -r '.idToken')
echo "$ID_TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq .
The JWT payload should include:
{
"sub": "user-uid",
"email": "test@example.com",
"firebase": {
"tenant": "socayo-dashboard-xxxxx",
"sign_in_provider": "password"
},
"iss": "https://securetoken.google.com/gym-hero-staging",
"aud": "gym-hero-staging"
}
Step 4: Create API Keys for the Tenant
Authenticate as an admin (JWT or existing secret key), then create keys for the tenant.
Create a publishable key (for client apps):
curl -X POST https://api.yocaso.dev/api/v1/api-keys/create \
-H "Authorization: Bearer <admin-jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "socayo-ios",
"description": "Socayo iOS app",
"keyType": "KEY_TYPE_PUBLISHABLE",
"publishableConfig": {
"oidc": {
"issuer": "https://securetoken.google.com/gym-hero-staging",
"jwksUrl": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com",
"audience": "gym-hero-staging"
}
}
}'
The userIdClaim field in the OIDC config specifies which JWT claim name to extract the user ID from (e.g., "sub", "email"). It is not the user ID value itself. If omitted, it defaults to "sub", which is correct for Firebase JWTs.
Create a secret key (for backend-to-backend):
curl -X POST https://api.yocaso.dev/api/v1/api-keys/create \
-H "Authorization: Bearer <admin-jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "socayo-backend",
"description": "Socayo backend server"
}'
The full key value is only returned once at creation time. Store it securely.
Step 5: Integrate Client Apps
iOS (Swift):
import FirebaseAuth
// Set tenant before any sign-in call
Auth.auth().tenantId = "socayo-dashboard-xxxxx"
// Sign in (Google, Apple, etc.) — standard Firebase flow
Auth.auth().signIn(with: credential) { result, error in
// JWT now includes firebase.tenant claim
result?.user.getIDToken { token, error in
// Use token + publishable key for API calls
}
}
Web (JavaScript):
import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth";
const auth = getAuth();
auth.tenantId = "socayo-dashboard-xxxxx";
const result = await signInWithPopup(auth, new GoogleAuthProvider());
const token = await result.user.getIdToken();
const response = await fetch("https://api.yocaso.dev/api/v1/llm/gateway/send-message", {
method: "POST",
headers: {
"X-API-Key": "pk_live_your_key_here",
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
conversationKey: "conv_abc",
userMessage: { role: "user", content: "Hello" },
}),
});
Tenant Isolation
Tenant boundaries are enforced at multiple layers:
| Layer | Mechanism |
|---|---|
| Identity | Firebase tenant user pools — users can only authenticate through their tenant |
| API Keys | Unkey externalId scopes keys to a tenant |
| JWT Validation | Publishable keys validate user JWTs against tenant-specific OIDC config |
| Request Context | X-Tenant-ID header propagated to all downstream services |
| Authorization | OpenFGA stores per tenant (planned) |
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
missing_credentials | No API key or JWT in request | Add X-API-Key or Authorization: Bearer header |
jwt_invalid_issuer | JWT issuer doesn't match FIREBASE_PROJECT_ID | Ensure the auth service and Firebase project match |
publishable_key_requires_jwt | Publishable key used without a user JWT | Add Authorization: Bearer <jwt> header |
user_id_claim_not_found | userIdClaim in OIDC config doesn't match a JWT claim | Omit userIdClaim (defaults to sub) or set it to a valid claim name like sub or email |
misconfigured_publishable_key | Publishable key missing OIDC metadata | Recreate the key with publishableConfig.oidc |
oidc_not_configured | Auth service OIDC validator not initialized | Check auth service logs for startup errors |
Roadmap
The current setup uses manual tenant onboarding. Future improvements:
- Self-service onboarding — new orgs create their own tenants via a dashboard
- Programmatic tenant management — Firebase Admin SDK integration for tenant CRUD
- Tenant-specific SSO — SAML/OIDC provider configuration per tenant
- OpenFGA integration — fine-grained authorization with per-tenant stores