Skip to main content

Functional Specification: SuperTokens Authentication with Multi-Tenancy Integration

1. Introduction & Goal

Problem Statement:

  • Current system uses Firebase Auth, creating vendor lock-in
  • Need to transition to self-hosted solution for full control
  • Multi-tenant architecture requires tenant-aware authentication
  • Demo user management needed for v0.1 with multiple tenant instances
  • Infrastructure transitioning from Firebase+GCP to Hetzner+Supabase+Cloudflare

Goal: Implement a self-hosted SuperTokens authentication system with complete multi-tenancy support, enabling:

  • User registration and login across multiple tenant organizations
  • Tenant-specific access control and data isolation
  • Demo user setup for v0.1 showcase
  • Seamless token management for web, CLI, and real-time clients

2. Current State

  • Authentication: Firebase Auth (temporary solution)
  • Multi-tenancy: Partially implemented (tenant_id in core tables, but auth layer is global)
  • Infrastructure: Transitioning from GCP → Hetzner, Firebase → SuperTokens
  • Database: PostgreSQL (multi-tenant ready)
  • Frontend: React (Firebase SDK integrated)
  • CLI: Basic auth with temporary tokens

3. Proposed Solution

Deploy a self-hosted SuperTokens Core service that integrates with our multi-tenant architecture:

Architecture Overview:

┌─────────────────────────────────────────────────────────┐
│ SuperTokens Core (Docker Container) │
│ (Handles: signup, login, sessions, token rotation) │
└──────────────────────────┬──────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ PostgreSQL (Tenant-Scoped User Association) │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ SuperTokens Tables │ │ Tenant-Users Table │ │
│ │ (auto-generated) │ │ (custom mapping) │ │
│ └──────────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Key Principles:

  • ✅ SuperTokens handles identity verification (authentication)
  • ✅ Custom tenant_users table tracks tenant associations
  • ✅ JWT claims include tenant context for isolation
  • ✅ GraphQL middleware enforces tenant-scoped access
  • ✅ RBAC roles managed per-tenant

4. Technical Design

4.1 Multi-Tenancy Architecture

User-Tenant Relationship:

SuperTokens provides global user management. We layer tenant association via custom metadata and a junction table:

-- Tracks SuperTokens user → Tenant mapping
CREATE TABLE tenant_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
supertokens_user_id VARCHAR(128) NOT NULL,
user_email VARCHAR(254) NOT NULL,
roles TEXT[] DEFAULT '{}', -- admin, s&op_orchestrator, analyst, viewer
organizational_unit_id UUID,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT unique_user_per_tenant UNIQUE(tenant_id, supertokens_user_id),
CONSTRAINT valid_roles CHECK (roles <@ ARRAY['admin', 's&op_orchestrator', 'analyst', 'viewer'])
);

CREATE INDEX idx_tenant_users_supertokens_id ON tenant_users(supertokens_user_id);
CREATE INDEX idx_tenant_users_email ON tenant_users(user_email);

Multi-Tenant User Setup:

When a user signs up:

  1. SuperTokens creates global user record
  2. Custom hook creates tenant_users entry with tenant_id
  3. JWT claim includes tenant_id for all subsequent requests
  4. All GraphQL queries filtered by tenant_id

4.2 Token Lifecycle

Web Clients (React):

  • Access Token: JWT, 15 minutes, stored in memory
  • Refresh Token: Opaque, 7 days, stored in HttpOnly cookie (secure!)
  • Flow: SuperTokens SDK handles automatic refresh in background
  • User Impact: No visible token management needed

CLI Client:

  • Token Storage: ~/.chainalign/tokens.json (mode 0600 for security)
  • Management: CLI requests new access token when expired
  • Flow: chainalign login → enter credentials → tokens stored locally
# CLI Token File Structure
{
"accessToken": "eyJhbGc...",
"refreshToken": "opaque-token-here",
"tenantId": "tenant-uuid",
"expiresAt": 1730644800000
}

WebSocket Connections (Real-Time):

  • Bearer Token: Access token passed in handshake
  • Verification: Checked on connect and renewed on refresh
  • Isolation: Tenant-scoped socket.io rooms

4.3 Authorization & Access Control

Role-Based Access Control (RBAC):

CREATE TABLE roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
name VARCHAR(50) NOT NULL,
description TEXT,

CONSTRAINT unique_role_per_tenant UNIQUE(tenant_id, name)
);

-- Predefined roles available in all tenants
INSERT INTO roles (tenant_id, name, description) VALUES
('*', 'admin', 'Full access to all features'),
('*', 's&op_orchestrator', 'Manage scenarios and constraints'),
('*', 'analyst', 'View and create reports'),
('*', 'viewer', 'Read-only access');

Roles stored per user:

  • Users have multiple roles: roles: ['admin', 's&op_orchestrator']
  • Roles are tenant-specific
  • Enforced at GraphQL resolver level via directives

GraphQL Authorization Example:

// Directive-based enforcement
const resolvers = {
Mutation: {
// Only admins and s&op_orchestrators can create scenarios
createScenario: requireRole(['admin', 's&op_orchestrator'])(
async (_, { input }, context) => {
// Automatic tenant_id filtering in context
return await ScenarioService.create(input, context.tenantId, context.userId);
}
),

// Only admins can delete
deleteScenario: requireRole(['admin'])(
async (_, { id }, context) => {
return await ScenarioService.delete(id, context.tenantId);
}
),
},
};

4.4 Tenant Context in Requests

Middleware Flow:

SuperTokens Middleware (verify session)

Tenant Middleware (extract tenant from JWT)

GraphQL Resolver (tenant_id enforced in queries)

Implementation:

// middleware/tenantMiddleware.js
export const tenantMiddleware = async (req, res, next) => {
const userId = req.session.getUserId();
const tenantId = req.session.getAccessTokenPayload().tenantId;

if (!tenantId) {
return res.status(403).json({ error: "User not associated with a tenant" });
}

req.tenantId = tenantId;
req.userId = userId;
next();
};

// All GraphQL queries automatically have tenant context

4.5 Real-Time Authentication (WebSocket)

Socket.io Auth Middleware:

// Verify token on connect
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
const session = await verifySession(token);

socket.userId = session.getUserId();
socket.tenantId = session.getAccessTokenPayload().tenantId;

next();
} catch (error) {
next(new Error('Invalid token'));
}
});

// Tenant-scoped rooms
io.on('connection', (socket) => {
socket.join(`tenant:${socket.tenantId}`);

// Broadcast only to users in same tenant
socket.on('scenario:update', (data) => {
io.to(`tenant:${socket.tenantId}`).emit('scenario:updated', data);
});
});

4.6 Demo User Setup for v0.1

Demo Tenant Structure:

TechElectronics Inc (tech-electronics-uuid)
├── admin@techemfg.com (admin, s&op_orchestrator)
├── analyst@techemfg.com (analyst)
└── viewer@techemfg.com (viewer)

PharmaCo (pharma-co-uuid)
├── admin@pharma.com (admin, s&op_orchestrator)
├── analyst@pharma.com (analyst)
└── viewer@pharma.com (viewer)

AgroScience (agro-science-uuid)
├── admin@agro.com (admin, s&op_orchestrator)
├── analyst@agro.com (analyst)
└── viewer@agro.com (viewer)

Seed Script:

#!/bin/bash
# Create users in SuperTokens and associate with tenants

DEMO_PASSWORD=${DEMO_PASSWORD:-"DemoPassword123!"}

# Create users via SuperTokens API
for email in admin@techemfg.com analyst@techemfg.com viewer@techemfg.com; do
curl -X POST "http://localhost:8007/recipe/user" \
-H "Content-Type: application/json" \
-d "{\"email\": \"$email\", \"password\": \"$DEMO_PASSWORD\"}"
done

# Associate with tenants via backend API
curl -X POST "http://api.chainalign.local/auth/assign-tenant" \
-H "Content-Type: application/json" \
-d "{
\"email\": \"admin@techemfg.com\",
\"tenantId\": \"tech-electronics-uuid\",
\"roles\": [\"admin\", \"s&op_orchestrator\"]
}"

4.7 Firebase → SuperTokens Migration

Migration Flow:

  1. Export Firebase users (with custom claims containing tenantId and roles)
  2. Create SuperTokens users
  3. Create tenant_users entries (preserving tenant and role mappings)
  4. Verify users can login with new system
  5. Decommission Firebase

Migration Script:

// Pseudo-code for migration
async function migrateFirebaseUsers() {
const firebaseUsers = await exportFirebaseUsers();

for (const fUser of firebaseUsers) {
// Create in SuperTokens
const stUser = await EmailPassword.signUpByEmail(fUser.email, tempPassword);

// Map tenant association
await tenantUsersRepo.create({
supertokensUserId: stUser.user.id,
tenantId: fUser.customClaims.tenantId,
roles: fUser.customClaims.roles || ['user'],
});
}
}

5. Implementation Phases

Phase 1: Infrastructure Setup

Deliverable: SuperTokens Core running in Docker, connected to PostgreSQL

Tasks:

  • Deploy SuperTokens Core Docker image
  • Configure PostgreSQL connection
  • Generate JWT signing keys (→ Infisical Vault)
  • Test basic signup/login

Blocking: None (can start immediately)

Phase 2: Backend Integration

Deliverable: GraphQL API with SuperTokens authentication and tenant context

Tasks:

  • Add supertokens-node SDK
  • Create tenant_users table and migrations
  • Implement signup hook for tenant association
  • Add session verification middleware
  • Add tenant context to GraphQL resolvers
  • Implement RBAC role enforcement

Blocking: Phase 1

Phase 3: Frontend Integration

Deliverable: React app with login/signup pages and protected routes

Tasks:

  • Add supertokens-auth-react SDK
  • Create auth routes (/auth/login, /auth/signup, /auth/reset)
  • Implement session authentication
  • Add route guards for protected pages
  • Test automatic token refresh

Blocking: Phase 1

Phase 4: CLI Token Management

Deliverable: CLI can login and maintain session without browser

Tasks:

  • Implement CLI login command (email/password → tokens)
  • Add token storage to ~/.chainalign/tokens.json
  • Implement automatic token refresh
  • Add logout command

Blocking: Phase 2 (needs backend auth endpoints)

Phase 5: WebSocket Authentication

Deliverable: Real-time features with per-tenant isolation

Tasks:

  • Implement socket.io auth middleware
  • Add token verification on connect
  • Set up tenant-scoped socket rooms
  • Test real-time updates with auth

Blocking: Phase 2 (needs token verification)

Phase 6: Demo User Setup

Deliverable: 3 demo tenants with 3 users each, ready for v0.1 showcase

Tasks:

  • Create demo tenants in database
  • Seed demo users via SuperTokens
  • Assign roles and tenant associations
  • Document demo credentials
  • Test all demo user flows

Blocking: Phase 2, Phase 3

Phase 7: Firebase Migration

Deliverable: All existing users migrated, Firebase decommissioned

Tasks:

  • Export Firebase users
  • Migrate to SuperTokens users
  • Create tenant_users mappings
  • Verify all users can login
  • Remove Firebase dependencies
  • Update secrets (remove FIREBASE_* from vault)

Blocking: Phase 2, Phase 3, Phase 6

6. Database Schema Summary

-- SuperTokens auto-generates (internal):
-- auth_user, auth_session, auth_emailpassword_users, etc.

-- Custom tables for multi-tenancy:
tenant_users (user-tenant mapping)
roles (role definitions per tenant)
role_permissions (role capabilities)
organizational_units (tenant structure)

7. Secrets Management Integration (M58)

SuperTokens Credentials for Infisical Vault:

SUPERTOKENS_CORE_URL=http://supertokens-core:3567
SUPERTOKENS_API_KEY=<random-secure-key>
SUPERTOKENS_JWT_SIGNING_KEY=<random-secure-key>

# OAuth (optional, for social login)
GOOGLE_OAUTH_CLIENT_ID=<from-google-cloud>
GOOGLE_OAUTH_CLIENT_SECRET=<from-google-cloud>

# Token Configuration
JWT_EXPIRY_SECONDS=900 # 15 minutes
REFRESH_TOKEN_EXPIRY=604800 # 7 days

Fallback Secrets Classification:

  • Required: SUPERTOKENS_CORE_URL, SUPERTOKENS_API_KEY, SUPERTOKENS_JWT_SIGNING_KEY
  • Optional: GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET

8. Security Considerations

Email Verification

  • Required before accessing application features
  • One-time verification links sent to user email
  • Prevents spam/fake accounts

Password Reset

  • Secure token-based flow
  • One-time use links with expiry
  • Rate-limited to prevent abuse

Rate Limiting

  • Auth endpoints protected: 5 attempts per 15 minutes per email
  • WebSocket connections: Token validation required
  • API endpoints: General rate limiting per tenant
  • HttpOnly: Prevents JavaScript access (XSS protection)
  • SameSite=Strict: CSRF protection
  • Secure: HTTPS only in production

Tenant Isolation

  • Critical: All queries must filter by tenant_id
  • JWT claim serves as "proof" of tenant membership
  • BaseRepository enforces tenant filter in all queries
  • GraphQL middleware validates tenant context

Protected Secrets

Never log these:

  • SUPERTOKENS_JWT_SIGNING_KEY
  • SUPERTOKENS_API_KEY
  • User passwords
  • Refresh tokens

8.5 User-Facing Error Handling

This section outlines the strategy for communicating authentication and authorization errors to the end-user. The goal is to provide a clear, secure, and helpful user experience without exposing internal system details that could be exploited.

Guiding Principles:

  • Be Generic: Avoid revealing specific reasons for failure (e.g., use "Invalid credentials" instead of "User not found" or "Incorrect password").
  • Be Actionable: When possible, guide the user on the next steps (e.g., "Need help? Contact support.").
  • Log Details: While user messages are generic, detailed contextual information must be logged internally for debugging.
  • Use HTTP Status Codes: Adhere to standard HTTP status codes to allow clients to react programmatically.

Error Response Structure:

All error responses from the API should follow a consistent JSON structure:

{
"error": {
"code": "AUTH_INVALID_CREDENTIALS",
"message": "The email or password you entered is incorrect. Please try again."
}
}

Common Error Scenarios:

ScenarioHTTP StatusError CodeUser-Facing MessageInternal Log Details
Login Failure401 UnauthorizedAUTH_INVALID_CREDENTIALSThe email or password you entered is incorrect. Please try again.Login attempt failed for email: [user_email]. Reason: [Invalid Password/User Not Found]
Email Already Exists409 ConflictAUTH_EMAIL_EXISTSAn account with this email address already exists. Please log in.Signup attempt failed for existing email: [user_email]
Forbidden Access403 ForbiddenAUTH_INSUFFICIENT_PERMISSIONSYou do not have permission to perform this action. Please contact your administrator if you believe this is an error.Access denied for user [userId] in tenant [tenantId]. Required roles: [required_roles], User roles: [user_roles]. Action: [action_name]
Session Expired401 UnauthorizedAUTH_SESSION_EXPIREDYour session has expired. Please log in again to continue.Session expired for user [userId]. Token expired at [expiry_time].
Tenant Mismatch403 ForbiddenAUTH_TENANT_ACCESS_DENIEDAccess to this resource is not allowed.User [userId] attempted to access a resource outside of their tenant [tenantId].
Rate Limit Exceeded429 Too Many RequestsAUTH_RATE_LIMIT_EXCEEDEDYou have made too many attempts. Please wait a few minutes and try again.Rate limit exceeded for IP [ip_address] on endpoint [endpoint].
Email Not Verified403 ForbiddenAUTH_EMAIL_NOT_VERIFIEDYour email address has not been verified. Please check your inbox for a verification link.Action blocked for unverified user [userId].
Generic Server Error500 Internal Server ErrorINTERNAL_SERVER_ERRORAn unexpected error occurred. Our team has been notified. Please try again later.[Full stack trace and error details]. Request ID: [request_id]

This structured approach ensures that users receive helpful feedback while the development team gets the detailed information needed to diagnose and resolve issues quickly.

9. Out of Scope

This FSD covers authentication (verifying identity). The following are explicitly out of scope:

  • Authorization Details: While RBAC structure is defined, specific permission implementation is per-feature
  • Infisical Vault Integration: Covered separately in M58 Secrets Management FSD
  • Advanced Features: MFA/2FA, SSO, SAML (can be added in future versions)
  • Infrastructure Deployment: Server/network setup is operations concern

10. Success Criteria

  • ✅ Users can signup, login, logout with email/password
  • ✅ SuperTokens session tokens are valid and verified
  • ✅ Multiple tenants can exist with isolated user bases
  • ✅ Same email can exist in different tenants
  • ✅ JWT claims include tenant_id for tenant context
  • ✅ GraphQL resolvers enforce tenant isolation
  • ✅ Demo users exist for 3 tenants (9 users total)
  • ✅ CLI can login and maintain session without browser
  • ✅ WebSocket connections require valid auth token
  • ✅ All protected secrets are masked in logs
  • ✅ Firebase users successfully migrated or decommissioned
  • ✅ No vendor lock-in: SuperTokens can be self-hosted on Hetzner

11. Dependencies & Prerequisites

External:

  • Hetzner VM with Docker support
  • PostgreSQL 13+ (shared with main application)
  • Google OAuth credentials (for social login, optional)
  • SMTP/Email service (SendGrid, AWS SES, etc.)

Internal (from other FSDSs):

  • M58 Secrets Management: Vault infrastructure and credentials
  • Database: Existing multi-tenant schema with tenant_id support
  • GraphQL API: Express+Apollo setup ready

Data:

  • Firebase user export (for migration)
  • Existing tenant definitions
  • M58 FSD: Secrets Management (vault credentials)
  • Database Schema: Multi-tenant table design
  • API Documentation: GraphQL authentication directives
  • Deployment Guide: SuperTokens Docker configuration

Version: 1.0 Status: Ready for Implementation Last Updated: 2025-11-03