Skip to content

Tenant Authentication Setup

Configure authentication for tenant APIs with JWT, Firebase Auth, or Google OAuth.

Overview

Tenants can configure their own authentication:

MethodDescriptionUse Case
JWTSelf-managed tokensCustom auth systems
Firebase AuthGoogle's auth serviceMobile/web apps
Google OAuthDirect Google loginEnterprise SSO
CustomAny OAuth2 providerThird-party auth

JWT Authentication

Tenant Configuration

json
{
  "jwt": {
    "secret": "{{secret:JWT_SECRET}}",
    "expiresIn": "24h",
    "algorithm": "HS256",
    "issuer": "tenant-api"
  }
}

Store JWT Secret

bash
POST /tenant/secrets
Authorization: Bearer <admin-token>
x-tenant-id: my-tenant

{
  "key": "JWT_SECRET",
  "value": "your-256-bit-secret-key-here"
}

Protected Routes

json
{
  "path": "/users/profile",
  "method": "get",
  "requireAuth": true,
  "response": {
    "user": "&#123;&#123;auth&#125;&#125;"
  }
}

Generate Tokens

Your app generates tokens:

javascript
import jwt from 'jsonwebtoken';

const token = jwt.sign(
  {
    sub: 'user123',
    email: 'user@example.com',
    role: 'user'
  },
  process.env.JWT_SECRET,
  { expiresIn: '24h', algorithm: 'HS256' }
);

API Calls

bash
curl -X GET https://api.example.com/users/profile \
  -H "Authorization: Bearer <jwt-token>" \
  -H "x-tenant-id: my-tenant"

JWT Claims Access

json
{
  "path": "/orders",
  "method": "get",
  "requireAuth": true,
  "supabaseQueries": [{
    "table": "orders",
    "operation": "select",
    "filters": [
      { "column": "user_id", "operator": "eq", "value": "&#123;&#123;auth.sub&#125;&#125;" }
    ]
  }]
}

Available claims:

  • &#123;&#123;auth.sub&#125;&#125; - Subject (user ID)
  • &#123;&#123;auth.email&#125;&#125; - User email
  • &#123;&#123;auth.role&#125;&#125; - User role
  • &#123;&#123;auth.tenant_id&#125;&#125; - Tenant ID (if in token)
  • &#123;&#123;auth.*&#125;&#125; - Any custom claim

Firebase Authentication

Configuration

json
{
  "firebase": {
    "projectId": "&#123;&#123;env.FIREBASE_PROJECT_ID&#125;&#125;",
    "privateKey": "&#123;&#123;secret:FIREBASE_PRIVATE_KEY&#125;&#125;",
    "clientEmail": "&#123;&#123;env.FIREBASE_CLIENT_EMAIL&#125;&#125;"
  },
  "auth": {
    "provider": "firebase"
  }
}

Store Firebase Credentials

bash
# Store private key as secret
POST /tenant/secrets
Authorization: Bearer <admin-token>
x-tenant-id: my-tenant

{
  "key": "FIREBASE_PRIVATE_KEY",
  "value": "-----BEGIN PRIVATE KEY-----\n..."
}

Protected Routes

json
{
  "path": "/dashboard",
  "method": "get",
  "requireAuth": true,
  "authProvider": "firebase"
}

Client-Side (React)

javascript
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';

const auth = getAuth();

// Sign in
const { user } = await signInWithEmailAndPassword(auth, email, password);

// Get ID token
const token = await user.getIdToken();

// Call API
const response = await fetch('/dashboard', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'x-tenant-id': 'my-tenant'
  }
});

Firebase Claims Access

json
{
  "path": "/user/data",
  "method": "get",
  "requireAuth": true,
  "authProvider": "firebase",
  "response": {
    "userId": "&#123;&#123;auth.uid&#125;&#125;",
    "email": "&#123;&#123;auth.email&#125;&#125;",
    "name": "&#123;&#123;auth.name&#125;&#125;"
  }
}

Firebase claims:

  • &#123;&#123;auth.uid&#125;&#125; - Firebase user ID
  • &#123;&#123;auth.email&#125;&#125; - User email
  • &#123;&#123;auth.email_verified&#125;&#125; - Email verified
  • &#123;&#123;auth.name&#125;&#125; - Display name
  • &#123;&#123;auth.picture&#125;&#125; - Profile photo URL

Google OAuth (Direct)

Configuration

json
{
  "oauth2": {
    "google": {
      "clientId": "&#123;&#123;env.GOOGLE_CLIENT_ID&#125;&#125;",
      "clientSecret": "&#123;&#123;secret:GOOGLE_CLIENT_SECRET&#125;&#125;",
      "callbackUrl": "https://api.example.com/auth/google/callback"
    }
  }
}

OAuth Routes

json
{
  "routes": [
    {
      "path": "/auth/google",
      "method": "get",
      "oauth2": {
        "provider": "google",
        "action": "authorize",
        "scopes": ["email", "profile"]
      }
    },
    {
      "path": "/auth/google/callback",
      "method": "get",
      "oauth2": {
        "provider": "google",
        "action": "callback",
        "onSuccess": {
          "redirect": "https://app.example.com/dashboard"
        }
      }
    }
  ]
}

Token Exchange Flow

  1. User visits /auth/google
  2. Redirected to Google consent screen
  3. Google redirects to /auth/google/callback
  4. Backflow exchanges code for tokens
  5. Creates session/JWT and redirects to app

Google User Info

json
{
  "path": "/auth/google/callback",
  "method": "get",
  "oauth2": {
    "provider": "google",
    "action": "callback"
  },
  "workflow": {
    "steps": [
      {
        "id": "upsert-user",
        "type": "database",
        "params": {
          "table": "users",
          "operation": "upsert",
          "data": {
            "google_id": "&#123;&#123;oauth.user.id&#125;&#125;",
            "email": "&#123;&#123;oauth.user.email&#125;&#125;",
            "name": "&#123;&#123;oauth.user.name&#125;&#125;",
            "picture": "&#123;&#123;oauth.user.picture&#125;&#125;"
          },
          "onConflict": ["google_id"]
        }
      },
      {
        "id": "generate-jwt",
        "type": "auth",
        "params": {
          "action": "createToken",
          "claims": {
            "sub": "&#123;&#123;steps.upsert-user.result.id&#125;&#125;",
            "email": "&#123;&#123;oauth.user.email&#125;&#125;"
          }
        }
      }
    ]
  }
}

Custom OAuth2 Provider

Configuration

json
{
  "oauth2": {
    "okta": {
      "clientId": "&#123;&#123;env.OKTA_CLIENT_ID&#125;&#125;",
      "clientSecret": "&#123;&#123;secret:OKTA_CLIENT_SECRET&#125;&#125;",
      "authorizeUrl": "https://your-domain.okta.com/oauth2/v1/authorize",
      "tokenUrl": "https://your-domain.okta.com/oauth2/v1/token",
      "userInfoUrl": "https://your-domain.okta.com/oauth2/v1/userinfo",
      "callbackUrl": "https://api.example.com/auth/okta/callback",
      "scopes": ["openid", "profile", "email"]
    }
  }
}

Routes

json
{
  "routes": [
    {
      "path": "/auth/okta",
      "method": "get",
      "oauth2": {
        "provider": "okta",
        "action": "authorize"
      }
    },
    {
      "path": "/auth/okta/callback",
      "method": "get",
      "oauth2": {
        "provider": "okta",
        "action": "callback"
      }
    }
  ]
}

RBAC (Role-Based Access Control)

Configure Roles

json
{
  "rbac": {
    "enabled": true,
    "roles": {
      "admin": {
        "permissions": ["*"]
      },
      "manager": {
        "permissions": ["read:*", "write:orders", "write:products"]
      },
      "user": {
        "permissions": ["read:own", "write:own:profile"]
      }
    }
  }
}

Protected Routes with Roles

json
{
  "path": "/admin/users",
  "method": "get",
  "requireAuth": true,
  "rbac": {
    "roles": ["admin"]
  }
}

Permission-Based Access

json
{
  "path": "/products",
  "method": "post",
  "requireAuth": true,
  "rbac": {
    "permissions": ["write:products"]
  }
}

Role from JWT

json
{
  "path": "/dashboard",
  "method": "get",
  "requireAuth": true,
  "rbac": {
    "rolesClaim": "role",
    "roles": ["admin", "manager"]
  }
}

JWT should include role:

json
{
  "sub": "user123",
  "role": "manager"
}

MFA (Multi-Factor Authentication)

Enable MFA

json
{
  "mfa": {
    "enabled": true,
    "methods": ["totp", "sms"],
    "requiredFor": ["admin"]
  }
}

MFA Enrollment

json
{
  "path": "/auth/mfa/enroll",
  "method": "post",
  "requireAuth": true,
  "mfa": {
    "action": "enroll",
    "method": "totp"
  }
}

MFA Verification

json
{
  "path": "/auth/mfa/verify",
  "method": "post",
  "requireAuth": true,
  "mfa": {
    "action": "verify"
  }
}

API Key Authentication

Generate API Key

json
{
  "path": "/admin/api-keys",
  "method": "post",
  "requireAuth": true,
  "rbac": { "roles": ["admin"] },
  "workflow": {
    "steps": [{
      "id": "create-key",
      "type": "auth",
      "params": {
        "action": "createApiKey",
        "name": "&#123;&#123;body.name&#125;&#125;",
        "permissions": "&#123;&#123;body.permissions&#125;&#125;",
        "expiresIn": "&#123;&#123;body.expiresIn&#125;&#125;"
      }
    }]
  }
}

API Key Protected Route

json
{
  "path": "/api/data",
  "method": "get",
  "auth": {
    "type": "api_key",
    "header": "X-API-Key"
  }
}

Usage:

bash
curl -X GET https://api.example.com/api/data \
  -H "X-API-Key: bf_live_xxx"

Complete Example

Tenant Config

json
{
  "tenantId": "fitness-studio",

  "jwt": {
    "secret": "&#123;&#123;secret:JWT_SECRET&#125;&#125;",
    "expiresIn": "7d"
  },

  "firebase": {
    "projectId": "&#123;&#123;env.FIREBASE_PROJECT_ID&#125;&#125;",
    "privateKey": "&#123;&#123;secret:FIREBASE_PRIVATE_KEY&#125;&#125;",
    "clientEmail": "&#123;&#123;env.FIREBASE_CLIENT_EMAIL&#125;&#125;"
  },

  "rbac": {
    "enabled": true,
    "roles": {
      "owner": { "permissions": ["*"] },
      "instructor": { "permissions": ["read:*", "write:classes", "write:bookings"] },
      "member": { "permissions": ["read:classes", "write:own:bookings"] }
    }
  },

  "routes": [
    {
      "path": "/classes",
      "method": "get",
      "requireAuth": true
    },
    {
      "path": "/classes",
      "method": "post",
      "requireAuth": true,
      "rbac": { "roles": ["owner", "instructor"] }
    },
    {
      "path": "/admin/members",
      "method": "get",
      "requireAuth": true,
      "rbac": { "roles": ["owner"] }
    }
  ]
}

Client Integration

javascript
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

// Sign in
const { user } = await signInWithEmailAndPassword(auth, email, password);
const token = await user.getIdToken();

// API client
const api = {
  async get(path) {
    return fetch(`https://api.example.com${path}`, {
      headers: {
        'Authorization': `Bearer ${token}`,
        'x-tenant-id': 'fitness-studio'
      }
    }).then(r => r.json());
  },

  async post(path, data) {
    return fetch(`https://api.example.com${path}`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'x-tenant-id': 'fitness-studio',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    }).then(r => r.json());
  }
};

// Usage
const classes = await api.get('/classes');
await api.post('/bookings', { classId: 'yoga-101' });

Released under the ISC License.