Source:
ocean/docs/adr/0052-authentication-flow-and-organization-provisioning.md| ✏️ Edit on GitHub
ADR-052: Authentication Flow and Organization Provisioning
Status
Accepted
Context
The Ocean platform uses a multi-tenant architecture where every user belongs to an organization. Understanding the authentication and provisioning flow is critical for debugging issues and maintaining the system.
Decision
Authentication Flow
The system follows this specific flow for user registration:
Key Design Principles
-
Organization-First Architecture
- Every user MUST belong to an organization
- Organizations are created during user signup
- No orphaned users without organizations
-
User-Organization Relationship
- First user becomes the organization owner
- Organizations table has
owner_idreferencingauth.users - This creates a dependency: user must exist before organization
-
Atomic Provisioning
- All provisioning happens in the
handle_new_usertrigger - If any step fails, the entire provisioning fails
- Failures are logged to
provisioning_eventstable
- All provisioning happens in the
Database Schema Relationships
auth.users (Supabase managed)
↓ (trigger on insert)
handle_new_user() function creates:
↓
organizations
- id: UUID (PK)
- owner_id: UUID (FK → auth.users.id)
- name: TEXT
- slug: TEXT (unique)
↓
profiles
- id: UUID (PK, FK → auth.users.id)
- organization_id: UUID (FK → organizations.id)
- email: TEXT
↓
organization_members
- organization_id: UUID (FK → organizations.id)
- user_id: UUID (FK → auth.users.id)
- role: 'owner' | 'admin' | 'member'
- status: 'active' | 'pending' | 'suspended'
Critical Fields in Signup
When a user signs up, these fields are passed in raw_user_meta_data:
organization_nameororganization: Organization namefirst_name: User's first namelast_name: User's last namedata_region: Where to host the tenant database (defaults to 'us-east-1')
Error Handling
All errors in provisioning are logged to provisioning_events with:
status: 'failed'error_message: The actual errorerror_context: Debug information about what was attempted
Consequences
Positive
- Clear separation between authentication (Supabase) and application logic
- Atomic provisioning ensures data consistency
- Organization-based security from the start
- Audit trail of all provisioning attempts
Negative
- Complex trigger logic that must be maintained
- Schema drift between environments can break provisioning
- Tight coupling between auth and business logic
- Debugging requires checking multiple tables
Trade-offs
- We chose trigger-based provisioning over application-level to ensure no user can exist without an organization
- This makes the system more rigid but ensures data integrity
- Schema changes require careful migration planning
Current Issues (September 2025)
Schema Drift
Production and local environments have diverged:
- Column naming differences (
errorvserror_message) - Missing columns (
statusin organization_members) - Check constraint differences (allowed data_region values)
- Function visibility issues (
generate_unique_slug)
Why Account Creation Fails
-
Production Issues (Now Fixed):
- ✅
handle_new_usertried to insert intoerrorcolumn but table haderror_message - ✅ Missing columns:
trial_ends_at,seats_included,seats_used - ✅ Function parameter mismatch in
generate_unique_slug
- ✅
-
Local Environment Issues:
- ❌ Missing
statuscolumn inorganization_members - ❌ Invalid default
data_regionvalue - ❌ Function permission issues
- ❌ NOT NULL constraint on
provisioning_events.organization_id
- ❌ Missing
The root cause is that migrations were applied directly to production without being committed to version control, causing schema drift.