Skip to main content

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

TermDescription
Firebase ProjectSingle GCP project (gym-hero-staging) hosting all tenants
Firebase TenantIsolated user pool within the project (e.g., socayo-dashboard)
Platform Tenant IDDerived 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:

  1. Auth service verifies the publishable key via Unkey
  2. Retrieves the OIDC config stored in the key's metadata
  3. Validates the user JWT against that OIDC config (issuer, audience, signature)
  4. Extracts tenant_id from the key, user_id from the JWT
  5. 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:

  1. Go to Identity Platform > Settings > Security > enable Allow tenants
  2. Go to Tenants > Add tenant
  3. Name it <tenant-name>-dashboard (e.g., socayo-dashboard)
  4. Enable sign-in providers on the tenant (Google, Apple, Email/Password, etc.)
  5. Note the tenant ID Firebase assigns

Step 2: Create a Test User

  1. Select your tenant in the Identity Platform console
  2. Click Add user
  3. 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"
}
}
}'
caution

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"
}'
warning

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:

LayerMechanism
IdentityFirebase tenant user pools — users can only authenticate through their tenant
API KeysUnkey externalId scopes keys to a tenant
JWT ValidationPublishable keys validate user JWTs against tenant-specific OIDC config
Request ContextX-Tenant-ID header propagated to all downstream services
AuthorizationOpenFGA stores per tenant (planned)

Troubleshooting

ErrorCauseFix
missing_credentialsNo API key or JWT in requestAdd X-API-Key or Authorization: Bearer header
jwt_invalid_issuerJWT issuer doesn't match FIREBASE_PROJECT_IDEnsure the auth service and Firebase project match
publishable_key_requires_jwtPublishable key used without a user JWTAdd Authorization: Bearer <jwt> header
user_id_claim_not_founduserIdClaim in OIDC config doesn't match a JWT claimOmit userIdClaim (defaults to sub) or set it to a valid claim name like sub or email
misconfigured_publishable_keyPublishable key missing OIDC metadataRecreate the key with publishableConfig.oidc
oidc_not_configuredAuth service OIDC validator not initializedCheck 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