UNPKG

oneie

Version:

Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.

1,897 lines 58.6 kB
---
title: Better Auth Any Backend
dimension: things
category: plans
tags: architecture, auth, backend, connections, convex, events, frontend, groups, ontology, people
related_dimensions: connections, events, groups, people
scope: global
created: 2025-11-03
updated: 2025-11-03
version: 1.0.0
ai_context: |
  This document is part of the things dimension in the plans category.
  Location: one/things/plans/better-auth-any-backend.md
  Purpose: Documents better auth + one ontology: universal authentication
  Related dimensions: connections, events, groups, people
  For AI agents: Read this to understand better auth any backend.
---

# Better Auth + ONE Ontology: Universal Authentication

**Integrating Better Auth with the ONE 6-dimension ontology using Effect.ts**

---

## Executive Summary

Better Auth is already integrated in our `/frontend` with 6 authentication methods. This plan shows how to make it work with **the ONE 6-dimension ontology** and **ANY backend** (Convex, Supabase, Neon, WordPress) using Effect.ts and the DataProvider pattern.

**Key Insight:** Auth maps perfectly to the 6 dimensions: groups partition auth data, people are the authorized users, sessions are connections, auth actions are events, and all operations use Effect.ts for type-safety.

**Architecture:**

```
┌─────────────────────────────────────────────────────────┐
│         Frontend (Astro + React)                        │
│  - Better Auth UI (6 auth methods)                     │
│  - Effect.ts AuthService (type-safe)                   │
│  - useEffectRunner hook                                │
│  - Backend-agnostic via DataProvider                   │
└────────────────┬────────────────────────────────────────┘
                 │ Effect.ts + DataProvider
                 ↓
┌─────────────────────────────────────────────────────────┐
│         AuthService (Effect.ts)                         │
│  - signUp → creates person (dimension 2) + event       │
│  - signIn → creates session connection + event         │
│  - signOut → deletes session connection + event        │
│  - All operations group-scoped & backend-agnostic      │
└────────────────┬────────────────────────────────────────┘
                 │ DataProvider Interface
                 ↓
┌─────────────────────────────────────────────────────────┐
│         ONE 6-Dimension Ontology                        │
│                                                         │
│  1. Groups: Multi-tenant partitioning                   │
│     - Each auth operation scoped to groupId            │
│     - Perfect data isolation                           │
│                                                         │
│  2. People: Users & authorization                       │
│     - type: "creator" thing with role property         │
│     - properties: { email, emailVerified, role }       │
│     - roles: platform_owner, group_owner, group_user   │
│                                                         │
│  3. Things: Session devices (optional)                  │
│     - type: "device" for session tracking              │
│                                                         │
│  4. Connections: Sessions & OAuth links                 │
│     - type: "session" → Active user sessions           │
│     - type: "oauth_account" → OAuth providers          │
│     - metadata: { token, expiresAt, provider }         │
│                                                         │
│  5. Events: Complete auth audit trail                   │
│     - type: "sign_up", "sign_in", "sign_out"          │
│     - actorId: userId (always required)                │
│     - groupId: which group (always required)           │
│                                                         │
│  6. Knowledge: Auth patterns for AI                     │
│     - Login behavior patterns                          │
│     - Security anomaly detection                       │
│     - User preference learning                         │
└────────────────┬────────────────────────────────────────┘
                 │
     ┌───────────┼───────────┬───────────┬───────────┐
     ↓           ↓           ↓           ↓           ↓
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Convex  │ │Supabase │ │   Neon   │ │WordPress │ │ Notion   │
│Provider │ │Provider │ │ Provider │ │ Provider │ │ Provider │
└─────────┘ └─────────┘ └──────────┘ └──────────┘ └──────────┘
```

**What This Achieves:**

- ✅ Organizations → multi-tenant auth (perfect isolation)
- ✅ People → users with roles (authorization built-in)
- ✅ Things → optional device tracking
- ✅ Connections → sessions & OAuth (automatic cleanup)
- ✅ Events → complete audit trail (security & analytics)
- ✅ Knowledge → AI learns auth patterns
- ✅ Effect.ts → type-safe, composable operations
- ✅ Backend-agnostic → swap providers with one line

---

## Table of Contents

1. [ONE Ontology Mapping](#one-ontology-mapping)
2. [Effect.ts AuthService](#effectts-authservice)
3. [DataProvider Integration](#dataprovider-integration)
4. [Backend Implementations](#backend-implementations)
5. [React Components](#react-components)
6. [Session Management](#session-management)
7. [Multi-Tenancy](#multi-tenancy)
8. [Better Auth Compatibility Layer](#better-auth-compatibility-layer)
9. [Testing](#testing)
10. [Migration Strategy](#migration-strategy)

---

## ONE 6-Dimension Mapping

**Better Auth traditionally uses 4 separate tables. In ONE, we map these to the 6-dimension ontology.**

### 1. Organizations → Multi-Tenant Isolation

```typescript
// Dimension 1: Organizations
// Every auth operation belongs to an organization
{
  _id: Id<'organizations'>,
  name: string,
  slug: string,                       // URL-friendly (e.g., "fitnesspro")
  status: 'active' | 'suspended',
  plan: 'starter' | 'pro' | 'enterprise',
  limits: {
    users: number,                    // Max users in this org
    storage: number,
    apiCalls: number,
    cycle: number
  },
  settings: {
    allowSignups: boolean,            // Can users self-register?
    requireEmailVerification: boolean,
    enableTwoFactor: boolean,
    allowedDomains?: string[]        // Email domain restrictions
  },
  createdAt: number,
  updatedAt: number
}
```

**Benefits:**

- ✅ Perfect multi-tenant isolation → users in Org A can't see Org B
- ✅ Per-org auth settings → different signup rules per org
- ✅ Per-org quotas → user limits, rate limits
- ✅ Per-org billing → track auth costs per organization

### 2. People → Separate Table (Dimension 2)

```typescript
// Dimension 2: People (SEPARATE TABLE)
// Better Auth "user" → ONE "people" table
{
  _id: Id<'people'>,                    // NOT Id<'things'>!
  email: string,                        // Better Auth: email
  username: string,                     // Unique username
  displayName: string,                  // Display name
  emailVerified: boolean,               // Better Auth: emailVerified
  password?: string,                    // Hashed (if email/password auth)
  image?: string,                       // Avatar URL

  // CRITICAL: Role determines access level
  role: "platform_owner" | "group_owner" | "group_user" | "customer",

  // Organization context
  groupId: Id<'groups'>,  // Primary group
  organizations: Id<'organizations'>[],  // All orgs this person belongs to
  permissions?: string[],               // Fine-grained permissions

  // 2FA
  twoFactorEnabled?: boolean,
  twoFactorSecret?: string,
  backupCodes?: string[],

  // OAuth (stored in connections, but basic info here)
  oauthProviders?: Array<{
    provider: "google" | "github" | "apple",
    providerId: string
  }>,

  // Profile
  bio?: string,
  location?: string,
  website?: string,

  createdAt: number,
  updatedAt: number
}
```

**Why People Are NOT Things:**

1. **Different Purpose:**
   - **People** → Authorization & governance (who can do what)
   - **Things** → Domain entities (what exists in the system)

2. **Different Relationships:**
   - **People** → Own things, act on things, authorize actions
   - **Things** → Owned by people, acted upon by people

3. **Different Lifecycle:**
   - **People** → Permanent identity, cross-org membership
   - **Things** → Created/deleted within org context

**Benefits:**

- ✅ People are first-class dimension → clear authorization model
- ✅ Role-based access → platform_owner can access all orgs
- ✅ Multi-org membership → same person, multiple organizations
- ✅ People act on things → actorId in events always references people
- ✅ Clean separation → auth logic vs domain logic

### 3. Things → Optional Device Tracking

```typescript
// Dimension 3: Things
// Optional: Track devices/browsers for session management
{
  _id: Id<'things'>,
  thingType: "device",
  name: string,                         // E.g., "iPhone 15 Pro - Safari"
  organizationId: Id<'organizations'>,
  status: "active",
  properties: {
    userAgent: string,
    deviceType: "mobile" | "desktop" | "tablet",
    os: string,                         // "iOS 17.5"
    browser: string,                    // "Safari 17"
    ipAddress?: string,
    lastSeen: number
  },
  createdAt: number,
  updatedAt: number
}
```

**Benefits:**

- ✅ Track which devices user is logged in from
- ✅ Security: Detect suspicious login locations
- ✅ UX: "Sign out from all devices except this one"

### 4. Connections → Sessions & OAuth Links

```typescript
// Dimension 4: Connections
// Better Auth "session" → ONE connection (type: "session")
{
  _id: Id<'connections'>,
  fromPersonId: Id<'people'>,           // Dimension 2: Person (NOT thing!)
  toThingId: Id<'things'> | null,       // Dimension 3: Device (optional)
  relationshipType: "session",          // Dimension 4: Connection type
  organizationId: Id<'organizations'>,  // Dimension 1: Org scope
  status: "active" | "expired" | "revoked",
  metadata: {
    token: string,                      // Session token (hashed)
    expiresAt: number,                  // Unix timestamp
    ipAddress?: string,
    userAgent?: string,
    method: "email" | "google" | "github" | "magic_link",
    lastActivity: number,               // Track session activity
    revokedAt?: number,
    revokedReason?: string
  },
  createdAt: number,
  updatedAt: number
}
```

**Benefits:**

- ✅ Sessions connect people to devices → clear actor model
- ✅ Automatic cleanup → delete expired connections
- ✅ Real-time sync → revoke session = instant logout everywhere
- ✅ Analytics → count active sessions per person
- ✅ Security → detect concurrent sessions from different locations

**OAuth Account Connection:**

```typescript
// Better Auth "account" → ONE connection (type: "oauth_account")
{
  _id: Id<'connections'>,
  fromPersonId: Id<'people'>,           // Dimension 2: Person (NOT thing!)
  toThingId: Id<'things'>,              // OAuth provider thing
  relationshipType: "oauth_account",    // Dimension 4
  organizationId: Id<'organizations'>,  // Dimension 1
  status: "active" | "revoked",
  metadata: {
    provider: "google" | "github" | "apple",
    providerAccountId: string,          // External user ID
    accessToken?: string,               // Encrypted
    refreshToken?: string,              // Encrypted
    idToken?: string,                   // JWT from provider
    tokenExpiresAt?: number,
    scope?: string,                     // OAuth scopes granted
    lastSync: number                    // Last token refresh
  },
  createdAt: number,
  updatedAt: number
}
```

**Benefits:**

- ✅ OAuth accounts connect people to OAuth providers
- ✅ Multiple providers → person can have Google + GitHub + Apple
- ✅ Token refresh → update connection metadata
- ✅ Revocation → disconnect OAuth provider

**Note on Schema Design:**
The connection schema may need to support both:

- `fromPersonId: Id<'people'>` for people → thing connections
- `fromThingId: Id<'things'>` for thing → thing connections

Or use a union type with discriminator for flexibility.

### 5. Events → Complete Auth Audit Trail

**Verification Tokens as Events:**

```typescript
// Dimension 5: Events
// Better Auth "verification" → ONE event (type: "verification_token_created")
{
  _id: Id<'events'>,
  eventType: "verification_token_created",
  actorId: Id<'people'>,                  // Dimension 2: Person who requested it
  targetId?: Id<'people'>,                // Dimension 2: Person being verified
  organizationId: Id<'organizations'>,    // Dimension 1: Org scope
  metadata: {
    identifier: string,                   // Email or phone
    token: string,                        // Verification token (hashed)
    verificationType: "email_verification" | "password_reset" | "magic_link",
    expiresAt: number,
    used: boolean,                        // Consumed?
    usedAt?: number,
    ipAddress?: string,
    userAgent?: string
  },
  timestamp: number,
  createdAt: number
}
```

**All Auth Actions as Events:**

```typescript
// Log ALL auth actions for complete audit trail
{
  _id: Id<'events'>,
  eventType: "sign_up" | "sign_in" | "sign_out" | "password_reset" | "email_verified" | "oauth_linked",
  actorId: Id<'people'>,                  // Dimension 2: Person who did it (REQUIRED)
  targetId?: Id<'things'>,                // Dimension 3: Target (session connection, device)
  organizationId: Id<'organizations'>,    // Dimension 1: Org scope (REQUIRED)
  metadata: {
    method: "email" | "google" | "github" | "magic_link",
    ipAddress: string,
    userAgent: string,
    success: boolean,
    errorReason?: string,                 // If failed
    sessionId?: Id<'connections'>,        // Reference to session connection
    deviceId?: Id<'things'>,              // Reference to device thing
    location?: {                          // Geo data
      country: string,
      city: string,
      coordinates?: [number, number]
    },
    riskScore?: number                    // Security risk assessment (0-100)
  },
  timestamp: number,
  createdAt: number
}
```

**Benefits:**

- ✅ Complete audit trail → every auth action logged with actorId
- ✅ Security analytics → detect suspicious patterns (impossible travel, brute force)
- ✅ User timeline → see complete login history
- ✅ Compliance → GDPR, SOC2 audit requirements
- ✅ Debugging → trace auth issues with full context
- ✅ Real-time monitoring → stream events to security tools

### 6. Knowledge → Auth Pattern Intelligence

```typescript
// Dimension 6: Knowledge
// Embed auth events & user behavior for AI-powered security
{
  _id: Id<'knowledge'>,
  knowledgeType: "auth_behavior_pattern",
  text: string,                           // Human-readable description
  embedding: number[],                    // Vector embedding (768-dim)
  embeddingModel: "text-embedding-3-small",
  sourcePersonId: Id<'people'>,           // Dimension 2: Person this pattern belongs to
  organizationId: Id<'organizations'>,    // Dimension 1: Org scope
  chunk: {
    personId: Id<'people'>,               // Person ID
    eventType: string,
    timestamp: number,
    ipAddress: string,
    location: { country: string, city: string },
    deviceType: string,
    success: boolean
  },
  labels: [
    "auth:login_pattern",
    "security:normal_behavior",
    "time:weekday_morning"
  ],
  metadata: {
    behaviorType: "normal" | "suspicious" | "anomalous",
    confidence: number,                   // 0-1
    riskFactors?: string[],               // ["new_device", "new_location"]
    relatedEvents: Id<'events'>[]         // Related auth events
  },
  createdAt: number,
  updatedAt: number
}
```

**Benefits:**

- ✅ AI learns normal auth patterns per user/org
- ✅ Detect anomalies: "User always logs in from NYC, now trying from Russia"
- ✅ Behavioral analysis: "User logs in Mon-Fri 9am-5pm, now 3am Sunday is suspicious"
- ✅ RAG for security: "Show me similar suspicious login patterns"
- ✅ Proactive alerts: "Unusual login activity detected"

**Example Use Cases:**

1. **Security Anomaly Detection:**

   ```typescript
   // Query knowledge for similar auth patterns
   const similarPatterns = await queryKnowledge({
     organizationId,
     query: "user login from new location",
     filters: {
       behaviorType: "suspicious",
       labels: ["security:anomaly"],
     },
     k: 5,
   });

   // If similar patterns were flagged before → increase risk score
   if (similarPatterns.length > 3) {
     riskScore += 30;
   }
   ```

2. **Person Preference Learning:**

   ```typescript
   // AI learns: "This person prefers Google OAuth, dislikes email/password"
   const personPreferences = await queryKnowledge({
     sourcePersonId: personId,
     labels: ["auth:preference"],
   });

   // Suggest preferred auth method in UI
   suggestedMethod = personPreferences[0]?.chunk.preferredMethod || "email";
   ```

3. **Compliance & Audit:**
   ```typescript
   // Vector search for all auth events matching audit criteria
   const auditEvents = await queryKnowledge({
     organizationId,
     query: "admin login from outside US",
     labels: ["compliance:geographic_restriction"],
     timeRange: { start: lastQuarter, end: now },
   });
   ```

---

## The Complete 6-Dimension Picture

**How all 6 dimensions work together for auth:**

```
1. Organization → "fitnesspro" (Id<'organizations'>)
   └─ Multi-tenant isolation boundary
   └─ Settings: allowSignups=true, requireEmailVerification=true

2. Person → "john@fitnesspro.com" (Id<'people'>)
   └─ Separate people table (NOT a thing!)
   └─ role: "org_user"
   └─ organizationId: fitnesspro_id

3. Thing → "John's iPhone" (Id<'things'>, type: "device")
   └─ Optional device tracking
   └─ properties: { os: "iOS 17", browser: "Safari" }

4. Connection → Session (Id<'connections'>)
   └─ fromPersonId: john_person_id (NOT thingId!)
   └─ toThingId: iphone_thing_id
   └─ type: "session", expiresAt: 7 days

5. Event → "sign_in" (Id<'events'>)
   └─ actorId: john_person_id (references people table)
   └─ method: "google", success: true
   └─ organizationId: fitnesspro_id

6. Knowledge → Auth pattern (Id<'knowledge'>)
   └─ sourcePersonId: john_person_id
   └─ AI learns: John always logs in from iPhone, weekdays, SF
```

**When John logs in from a new Android device in Moscow:**

1. **Organization**: Still "fitnesspro" ✅
2. **Person**: John (`Id<'people'>`) ✅
3. **Thing**: New "John's Android" device created (`Id<'things'>`, type: "device")
4. **Connection**: New session created
   - fromPersonId: john_person_id
   - toThingId: android_device_id
   - type: "session"
5. **Event**: "sign_in" logged
   - actorId: john_person_id (Person ID!)
   - targetId: android_device_id
   - metadata: { location: "Moscow, Russia" }
6. **Knowledge**: Vector search finds no similar patterns
   - AI flags as suspicious
   - Risk score: 85/100
   - 2FA required ⚠️

**Benefits of 6 Dimensions:**

- ✅ Perfect multi-tenant isolation (organizations)
- ✅ Clear authorization model (people with roles, NOT things)
- ✅ Device tracking (things)
- ✅ Session management (connections link people → devices)
- ✅ Complete audit trail (events with people as actors)
- ✅ AI-powered security (knowledge learns per person)

---

## Effect.ts AuthService

**Wrap all auth operations in Effect.ts for type-safety and composability.**

### AuthService Interface

```typescript
// frontend/src/services/AuthService.ts
import { Effect, Context } from "effect";
import { DataProvider, ThingNotFoundError } from "@/providers/DataProvider";

// Auth-specific errors
export class AuthenticationError {
  readonly _tag = "AuthenticationError";
  constructor(readonly reason: string) {}
}

export class InvalidCredentialsError {
  readonly _tag = "InvalidCredentialsError";
}

export class EmailAlreadyExistsError {
  readonly _tag = "EmailAlreadyExistsError";
  constructor(readonly email: string) {}
}

// User type (thing with type: "person")
export interface User {
  id: string;
  email: string;
  name: string;
  emailVerified: boolean;
  image?: string;
}

// Session type (connection with type: "session")
export interface Session {
  id: string;
  userId: string;
  token: string;
  expiresAt: number;
  method: "email" | "google" | "github";
}

// AuthService interface
export interface AuthServiceInterface {
  // Sign up → creates thing (type: person) + logs event
  signUp: (params: {
    email: string;
    password: string;
    name: string;
    organizationId?: string;
  }) => Effect.Effect<User, EmailAlreadyExistsError | Error>;

  // Sign in → creates connection (type: session) + logs event
  signIn: (params: {
    email: string;
    password: string;
    organizationId?: string;
  }) => Effect.Effect<
    { user: User; session: Session },
    InvalidCredentialsError | Error
  >;

  // OAuth sign in → creates thing + connection (oauth_account) + connection (session)
  signInWithOAuth: (params: {
    provider: "google" | "github" | "apple";
    code: string;
    organizationId?: string;
  }) => Effect.Effect<
    { user: User; session: Session },
    AuthenticationError | Error
  >;

  // Sign out → deletes connection (session) + logs event
  signOut: (sessionToken: string) => Effect.Effect<void, Error>;

  // Get current session → queries connection (type: session)
  getSession: (
    sessionToken: string,
  ) => Effect.Effect<{ user: User; session: Session } | null, Error>;

  // Verify email → updates thing properties.emailVerified + logs event
  verifyEmail: (token: string) => Effect.Effect<void, Error>;

  // Reset password → creates event (verification_token) → updates thing password
  requestPasswordReset: (email: string) => Effect.Effect<void, Error>;
  resetPassword: (params: {
    token: string;
    newPassword: string;
  }) => Effect.Effect<void, Error>;
}

// AuthService implementation (backend-agnostic via DataProvider)
export class AuthService extends Effect.Service<AuthService>()("AuthService", {
  effect: Effect.gen(function* () {
    const provider = yield* DataProvider;

    return {
      // Sign up: create user thing + log event
      signUp: (params) =>
        Effect.gen(function* () {
          // 1. Check if email exists
          const existing = yield* provider.things
            .list({
              type: "person",
              organizationId: params.organizationId,
              filters: { "properties.email": params.email },
            })
            .pipe(Effect.catchAll(() => Effect.succeed([])));

          if (existing.length > 0) {
            return yield* Effect.fail(
              new EmailAlreadyExistsError(params.email),
            );
          }

          // 2. Hash password (use bcrypt in real app)
          const hashedPassword = yield* hashPassword(params.password);

          // 3. Create user thing
          const userId = yield* provider.things.create({
            type: "person",
            name: params.name,
            organizationId: params.organizationId,
            properties: {
              email: params.email,
              emailVerified: false,
              password: hashedPassword,
            },
          });

          // 4. Log sign_up event
          yield* provider.events.log({
            type: "sign_up",
            actorId: userId,
            organizationId: params.organizationId,
            metadata: {
              method: "email",
              success: true,
            },
          });

          // 5. Return user
          return {
            id: userId,
            email: params.email,
            name: params.name,
            emailVerified: false,
          };
        }),

      // Sign in: verify credentials + create session connection
      signIn: (params) =>
        Effect.gen(function* () {
          // 1. Find user by email
          const users = yield* provider.things.list({
            type: "person",
            organizationId: params.organizationId,
            filters: { "properties.email": params.email },
          });

          if (users.length === 0) {
            return yield* Effect.fail(new InvalidCredentialsError());
          }

          const user = users[0];

          // 2. Verify password
          const valid = yield* verifyPassword(
            params.password,
            user.properties.password,
          );

          if (!valid) {
            // Log failed attempt
            yield* provider.events.log({
              type: "sign_in",
              actorId: user._id,
              organizationId: params.organizationId,
              metadata: {
                method: "email",
                success: false,
                errorReason: "invalid_credentials",
              },
            });

            return yield* Effect.fail(new InvalidCredentialsError());
          }

          // 3. Generate session token
          const token = yield* generateSecureToken();

          // 4. Create session connection
          const sessionId = yield* provider.connections.create({
            fromThingId: user._id,
            toThingId: null, // No device tracking yet
            relationshipType: "session",
            organizationId: params.organizationId,
            metadata: {
              token,
              expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days
              method: "email",
            },
          });

          // 5. Log successful sign_in event
          yield* provider.events.log({
            type: "sign_in",
            actorId: user._id,
            targetId: sessionId,
            organizationId: params.organizationId,
            metadata: {
              method: "email",
              success: true,
            },
          });

          return {
            user: {
              id: user._id,
              email: user.properties.email,
              name: user.name,
              emailVerified: user.properties.emailVerified,
              image: user.properties.image,
            },
            session: {
              id: sessionId,
              userId: user._id,
              token,
              expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000,
              method: "email",
            },
          };
        }),

      // Sign out: delete session connection + log event
      signOut: (sessionToken) =>
        Effect.gen(function* () {
          // 1. Find session connection by token
          const sessions = yield* provider.connections.list({
            relationshipType: "session",
            filters: { "metadata.token": sessionToken },
          });

          if (sessions.length === 0) {
            return; // Session already gone
          }

          const session = sessions[0];

          // 2. Delete session connection
          yield* provider.connections.delete(session._id);

          // 3. Log sign_out event
          yield* provider.events.log({
            type: "sign_out",
            actorId: session.fromThingId,
            targetId: session._id,
            metadata: {
              method: session.metadata.method,
              success: true,
            },
          });
        }),

      // Get session: query active session connection
      getSession: (sessionToken) =>
        Effect.gen(function* () {
          // 1. Find session connection by token
          const sessions = yield* provider.connections
            .list({
              relationshipType: "session",
              filters: { "metadata.token": sessionToken },
            })
            .pipe(Effect.catchAll(() => Effect.succeed([])));

          if (sessions.length === 0) {
            return null;
          }

          const session = sessions[0];

          // 2. Check expiration
          if (session.metadata.expiresAt < Date.now()) {
            // Session expired - delete it
            yield* provider.connections.delete(session._id);
            return null;
          }

          // 3. Get user thing
          const user = yield* provider.things.get(session.fromThingId);

          return {
            user: {
              id: user._id,
              email: user.properties.email,
              name: user.name,
              emailVerified: user.properties.emailVerified,
              image: user.properties.image,
            },
            session: {
              id: session._id,
              userId: user._id,
              token: session.metadata.token,
              expiresAt: session.metadata.expiresAt,
              method: session.metadata.method,
            },
          };
        }),

      // Additional methods: verifyEmail, requestPasswordReset, etc.
      // ... (similar pattern using provider.things, provider.events)
    };
  }),
  dependencies: [DataProvider],
}) {}

// Helper functions (use crypto libraries in production)
const hashPassword = (password: string) =>
  Effect.tryPromise({
    try: async () => {
      // Use bcrypt or argon2 in production
      return `hashed_${password}`;
    },
    catch: (error) => new Error(String(error)),
  });

const verifyPassword = (password: string, hash: string) =>
  Effect.tryPromise({
    try: async () => {
      // Use bcrypt.compare or argon2.verify in production
      return hash === `hashed_${password}`;
    },
    catch: (error) => new Error(String(error)),
  });

const generateSecureToken = () =>
  Effect.sync(() => {
    // Use crypto.randomBytes in production
    return Math.random().toString(36).substring(2, 15);
  });
```

**Key Points:**

- ✅ Uses `DataProvider` → backend-agnostic (works with Convex, Supabase, WordPress)
- ✅ All operations wrapped in `Effect.gen` → type-safe, composable
- ✅ Users stored as things (type: person)
- ✅ Sessions stored as connections (type: session)
- ✅ All actions logged as events
- ✅ Multi-tenant by default (groupId scoping)

---

## DataProvider Integration

**AuthService depends on DataProvider → works with ANY backend**

```typescript
// frontend/src/providers/DataProvider.ts
export interface DataProvider {
  things: {
    get: (id: string) => Effect.Effect<Thing, ThingNotFoundError>;
    list: (params: {
      type: ThingType;
      organizationId?: string;
      filters?: any;
    }) => Effect.Effect<Thing[], Error>;
    create: (input: {
      type: ThingType;
      name: string;
      properties: any;
    }) => Effect.Effect<string, Error>;
    update: (id: string, updates: Partial<Thing>) => Effect.Effect<void, Error>;
    delete: (id: string) => Effect.Effect<void, Error>;
  };

  connections: {
    create: (input: {
      fromThingId: string;
      toThingId: string | null;
      relationshipType: ConnectionType;
      metadata?: any;
    }) => Effect.Effect<string, Error>;
    list: (params: {
      relationshipType: ConnectionType;
      filters?: any;
    }) => Effect.Effect<Connection[], Error>;
    delete: (id: string) => Effect.Effect<void, Error>;
  };

  events: {
    log: (event: {
      type: EventType;
      actorId: string;
      targetId?: string;
      metadata?: any;
    }) => Effect.Effect<void, Error>;
  };
}
```

**Backend Providers implement this interface:**

### Convex Provider (Example)

```typescript
// frontend/src/providers/convex/ConvexProvider.ts
import { Effect, Layer } from "effect";
import { ConvexHttpClient } from "convex/browser";
import { DataProvider } from "../DataProvider";
import { api } from "./api";

export class ConvexProvider implements DataProvider {
  constructor(private client: ConvexHttpClient) {}

  things = {
    create: (input) =>
      Effect.tryPromise({
        try: () => this.client.mutation(api.things.create, input),
        catch: (error) => new Error(String(error)),
      }),

    list: (params) =>
      Effect.tryPromise({
        try: () => this.client.query(api.things.list, params),
        catch: (error) => new Error(String(error)),
      }),

    // ... other methods
  };

  connections = {
    create: (input) =>
      Effect.tryPromise({
        try: () => this.client.mutation(api.connections.create, input),
        catch: (error) => new Error(String(error)),
      }),

    // ... other methods
  };

  events = {
    log: (event) =>
      Effect.tryPromise({
        try: () => this.client.mutation(api.events.log, event),
        catch: (error) => new Error(String(error)),
      }),
  };
}

// Factory function
export const convexProvider = (config: { url: string }) =>
  Layer.succeed(
    DataProvider,
    new ConvexProvider(new ConvexHttpClient(config.url)),
  );
```

**Result:**

- AuthService calls `provider.things.create()` → works with Convex
- Change to `supabaseProvider()` → works with Supabase
- Change to `wordpressProvider()` → works with WordPress
- **Same AuthService code, different backend**

---

## React Components

**Use AuthService in components via `useEffectRunner` hook**

### Sign Up Component

```tsx
// frontend/src/components/auth/SignUpForm.tsx
import { useEffectRunner } from "@/hooks/useEffectRunner";
import { AuthService } from "@/services/AuthService";
import { Effect } from "effect";
import { useState } from "react";

export function SignUpForm({ organizationId }: { organizationId: string }) {
  const { run, loading, error } = useEffectRunner();
  const [formData, setFormData] = useState({
    email: "",
    password: "",
    name: "",
  });

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    // Define Effect program using AuthService
    const program = Effect.gen(function* () {
      const auth = yield* AuthService;

      // Sign up → creates thing (type: person) + logs event
      const user = yield* auth.signUp({
        ...formData,
        organizationId,
      });

      return user;
    }).pipe(
      // Handle specific errors
      Effect.catchTag("EmailAlreadyExistsError", (err) =>
        Effect.fail(new Error(`Email ${err.email} is already registered`)),
      ),
    );

    // Run Effect program
    const user = await run(program, {
      onSuccess: (user) => {
        // Redirect to verify email page
        window.location.href = "/verify-email";
      },
    });
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <input
        type="text"
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        placeholder="Full Name"
        required
      />

      <input
        type="email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        placeholder="Email"
        required
      />

      <input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
        placeholder="Password"
        required
      />

      {error && <div className="text-red-600">{error}</div>}

      <button type="submit" disabled={loading}>
        {loading ? "Creating account..." : "Sign Up"}
      </button>
    </form>
  );
}
```

### Sign In Component

```tsx
// frontend/src/components/auth/SignInForm.tsx
import { useEffectRunner } from "@/hooks/useEffectRunner";
import { AuthService } from "@/services/AuthService";
import { Effect } from "effect";
import { useState } from "react";

export function SignInForm({ organizationId }: { organizationId: string }) {
  const { run, loading, error } = useEffectRunner();
  const [formData, setFormData] = useState({
    email: "",
    password: "",
  });

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    // Define Effect program
    const program = Effect.gen(function* () {
      const auth = yield* AuthService;

      // Sign in → creates connection (type: session) + logs event
      const result = yield* auth.signIn({
        ...formData,
        organizationId,
      });

      return result;
    }).pipe(
      // Handle invalid credentials
      Effect.catchTag("InvalidCredentialsError", () =>
        Effect.fail(new Error("Invalid email or password")),
      ),
    );

    // Run Effect program
    await run(program, {
      onSuccess: ({ user, session }) => {
        // Store session token in cookie/localStorage
        document.cookie = `session=${session.token}; path=/; max-age=${7 * 24 * 60 * 60}`;

        // Redirect to dashboard
        window.location.href = "/dashboard";
      },
    });
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <input
        type="email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        placeholder="Email"
        required
      />

      <input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
        placeholder="Password"
        required
      />

      {error && <div className="text-red-600">{error}</div>}

      <button type="submit" disabled={loading}>
        {loading ? "Signing in..." : "Sign In"}
      </button>

      <a href="/forgot-password" className="text-sm text-blue-600">
        Forgot password?
      </a>
    </form>
  );
}
```

### OAuth Sign In Button

```tsx
// frontend/src/components/auth/OAuthButton.tsx
import { useEffectRunner } from '@/hooks/useEffectRunner'
import { AuthService } from '@/services/AuthService'
import { Effect } from 'effect'

interface Props {
  provider: 'google' | 'github' | 'apple'
  organizationId: string
}

export function OAuthButton({ provider, organizationId }: Props) {
  const { run, loading } = useEffectRunner()

  const handleOAuth = async () => {
    // 1. Redirect to OAuth provider
    const redirectUrl = `${window.location.origin}/auth/callback/${provider}`
    const authUrl = getOAuthUrl(provider, redirectUrl)

    window.location.href = authUrl
  }

  return (
    <button onClick={handleOAuth} disabled={loading}>
      {loading ? 'Redirecting...' : `Continue with ${capitalize(provider)}`}
    </button>
  )
}

// OAuth callback page
// frontend/src/pages/auth/callback/[provider].astro
---
import { AuthService } from '@/services/AuthService'
import { Effect } from 'effect'
import { ClientLayer } from '@/services/ClientLayer'

const { provider } = Astro.params
const code = Astro.url.searchParams.get('code')

if (!code) {
  return Astro.redirect('/signin?error=no_code')
}

// Sign in with OAuth
const program = Effect.gen(function* () {
  const auth = yield* AuthService

  return yield* auth.signInWithOAuth({
    provider: provider as 'google' | 'github',
    code,
    organizationId: Astro.locals.organizationId
  })
})

try {
  const { user, session } = await Effect.runPromise(
    program.pipe(Effect.provide(ClientLayer))
  )

  // Set session cookie
  Astro.cookies.set('session', session.token, {
    path: '/',
    maxAge: 7 * 24 * 60 * 60,
    httpOnly: true,
    secure: true
  })

  return Astro.redirect('/dashboard')
} catch (error) {
  return Astro.redirect(`/signin?error=${error.message}`)
}
---
```

**Key Points:**

- ✅ Components use `useEffectRunner` hook
- ✅ All auth operations wrapped in `Effect.gen`
- ✅ Typed error handling with `catchTag`
- ✅ Backend-agnostic → AuthService uses DataProvider
- ✅ Sessions stored as connections → automatic real-time sync

---

## Backend Implementations

**Each backend implements the ontology differently, but AuthService stays the same**

### Convex Backend

```typescript
// backend/convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  // Users = things with type: "person"
  things: defineTable({
    type: v.string(), // "person", "course", "product", etc.
    name: v.string(),
    organizationId: v.string(),
    status: v.string(),
    properties: v.any(), // { email, emailVerified, password, ... }
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index("by_type", ["type"])
    .index("by_organization", ["organizationId"])
    .index("by_email", ["properties.email"]), // For fast user lookup

  // Sessions = connections with relationshipType: "session"
  connections: defineTable({
    fromThingId: v.string(), // User ID
    toThingId: v.union(v.string(), v.null()), // Device ID (optional)
    relationshipType: v.string(), // "session", "oauth_account", etc.
    organizationId: v.string(),
    status: v.string(),
    metadata: v.any(), // { token, expiresAt, method, ... }
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index("by_type", ["relationshipType"])
    .index("by_from", ["fromThingId"])
    .index("by_token", ["metadata.token"]), // For fast session lookup

  // Auth events = events with type: "sign_up", "sign_in", etc.
  events: defineTable({
    type: v.string(), // "sign_up", "sign_in", "sign_out", etc.
    actorId: v.string(),
    targetId: v.optional(v.string()),
    organizationId: v.string(),
    metadata: v.any(),
    timestamp: v.number(),
    createdAt: v.number(),
  })
    .index("by_type", ["type"])
    .index("by_actor", ["actorId"])
    .index("by_timestamp", ["timestamp"]),
});
```

**Result:** Users, sessions, OAuth accounts all stored in the ontology!

### Supabase Backend

```sql
-- Users = things with type: "person"
CREATE TABLE things (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  type TEXT NOT NULL,
  name TEXT NOT NULL,
  "organizationId" TEXT NOT NULL,
  status TEXT NOT NULL,
  properties JSONB NOT NULL,  -- { email, emailVerified, password, ... }
  "createdAt" BIGINT NOT NULL,
  "updatedAt" BIGINT NOT NULL
);

CREATE INDEX idx_things_type ON things(type);
CREATE INDEX idx_things_email ON things((properties->>'email'));

-- Sessions = connections with relationshipType: "session"
CREATE TABLE connections (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  "fromThingId" TEXT NOT NULL,
  "toThingId" TEXT,
  "relationshipType" TEXT NOT NULL,
  "organizationId" TEXT NOT NULL,
  status TEXT NOT NULL,
  metadata JSONB NOT NULL,  -- { token, expiresAt, method, ... }
  "createdAt" BIGINT NOT NULL,
  "updatedAt" BIGINT NOT NULL
);

CREATE INDEX idx_connections_type ON connections("relationshipType");
CREATE INDEX idx_connections_token ON connections((metadata->>'token'));

-- Auth events = events
CREATE TABLE events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  type TEXT NOT NULL,
  "actorId" TEXT NOT NULL,
  "targetId" TEXT,
  "organizationId" TEXT NOT NULL,
  metadata JSONB NOT NULL,
  timestamp BIGINT NOT NULL,
  "createdAt" BIGINT NOT NULL
);

CREATE INDEX idx_events_type ON events(type);
CREATE INDEX idx_events_actor ON events("actorId");
```

**Result:** Same ontology, different database (PostgreSQL instead of Convex)

---

## Session Management

**Sessions are connections → automatic lifecycle management**

### Session Middleware

```typescript
// frontend/src/middleware.ts
import { defineMiddleware } from "astro:middleware";
import { AuthService } from "@/services/AuthService";
import { Effect } from "effect";
import { ClientLayer } from "@/services/ClientLayer";

export const onRequest = defineMiddleware(async (context, next) => {
  // Get session token from cookie
  const sessionToken = context.cookies.get("session")?.value;

  if (!sessionToken) {
    context.locals.user = null;
    context.locals.session = null;
    return next();
  }

  // Verify session using AuthService
  const program = Effect.gen(function* () {
    const auth = yield* AuthService;
    return yield* auth.getSession(sessionToken);
  });

  try {
    const result = await Effect.runPromise(
      program.pipe(Effect.provide(ClientLayer)),
    );

    if (result) {
      context.locals.user = result.user;
      context.locals.session = result.session;
    }
  } catch (error) {
    // Session invalid - clear cookie
    context.cookies.delete("session");
  }

  return next();
});
```

### Protected Routes

```astro
---
// frontend/src/pages/dashboard.astro
const user = Astro.locals.user

if (!user) {
  return Astro.redirect('/signin')
}
---

<Layout title="Dashboard">
  <h1>Welcome, {user.name}!</h1>
  <p>Email: {user.email}</p>
</Layout>
```

**Session Benefits:**

- ✅ Stored as connections → query active sessions
- ✅ Automatic expiration → status="expired" when expiresAt passed
- ✅ Real-time revocation → delete connection = logged out everywhere
- ✅ Analytics → count sessions per user, track login patterns

---

## Multi-Tenancy

**Users and sessions are automatically scoped to organizations**

### Organization-Based Sign Up

```typescript
// frontend/src/pages/[orgSlug]/signup.astro
---
import { SignUpForm } from '@/components/auth/SignUpForm'

// Get org from subdomain via middleware
const org = Astro.locals.organization

if (!org) {
  return Astro.redirect('/404')
}
---

<Layout title={`Sign up for ${org.name}`}>
  <h1>Create your {org.name} account</h1>

  <!-- SignUpForm automatically uses org.id -->
  <SignUpForm client:load organizationId={org._id} />
</Layout>
```

**How It Works:**

1. User visits `fitnesspro.one.ie/signup`
2. Middleware extracts `fitnesspro` → queries organization thing
3. Sign up form passes `organizationId` to AuthService
4. User thing created with `organizationId: fitnesspro_id`
5. Session connection created with `organizationId: fitnesspro_id`
6. All queries automatically filtered by `organizationId`

**Benefits:**

- ✅ Data isolation → users can't access other orgs
- ✅ Automatic scoping → all auth operations org-scoped
- ✅ Multi-org users → same email, different orgs
- ✅ Session tracking → see which org user is logged into

---

## Better Auth Compatibility Layer

**Optional: Wrap ONE's AuthService to match Better Auth API**

If you have existing Better Auth code and want to migrate gradually:

```typescript
// frontend/src/auth/better-auth-compat.ts
import { AuthService } from "@/services/AuthService";
import { Effect } from "effect";
import { ClientLayer } from "@/services/ClientLayer";

// Better Auth API compatibility layer
export const betterAuth = {
  api: {
    signUp: {
      email: async (params: {
        email: string;
        password: string;
        name: string;
      }) => {
        const program = Effect.gen(function* () {
          const auth = yield* AuthService;
          return yield* auth.signUp({
            ...params,
            organizationId: getCurrentOrgId(),
          });
        });

        return Effect.runPromise(program.pipe(Effect.provide(ClientLayer)));
      },
    },

    signIn: {
      email: async (params: { email: string; password: string }) => {
        const program = Effect.gen(function* () {
          const auth = yield* AuthService;
          return yield* auth.signIn({
            ...params,
            organizationId: getCurrentOrgId(),
          });
        });

        return Effect.runPromise(program.pipe(Effect.provide(ClientLayer)));
      },
    },

    signOut: async () => {
      const sessionToken = getSessionToken();
      if (!sessionToken) return;

      const program = Effect.gen(function* () {
        const auth = yield* AuthService;
        return yield* auth.signOut(sessionToken);
      });

      return Effect.runPromise(program.pipe(Effect.provide(ClientLayer)));
    },

    getSession: async () => {
      const sessionToken = getSessionToken();
      if (!sessionToken) return null;

      const program = Effect.gen(function* () {
        const auth = yield* AuthService;
        return yield* auth.getSession(sessionToken);
      });

      return Effect.runPromise(program.pipe(Effect.provide(ClientLayer)));
    },
  },
};

// Helper functions
function getCurrentOrgId(): string {
  // Extract from subdomain or context
  return globalThis.currentOrgId || "";
}

function getSessionToken(): string | null {
  // Read from cookie
  const match = document.cookie.match(/session=([^;]+)/);
  return match ? match[1] : null;
}
```

**Result:** Existing Better Auth UI components work with ONE ontology!

---

## Testing

**Effect.ts test layers make testing easy**

```typescript
// frontend/src/services/__tests__/AuthService.test.ts
import { describe, it, expect } from "vitest";
import { Effect, Layer } from "effect";
import { AuthService } from "../AuthService";
import { DataProvider } from "@/providers/DataProvider";

// Mock DataProvider
const mockThings: any[] = [];
const mockConnections: any[] = [];
const mockEvents: any[] = [];

const MockDataProvider = Layer.succeed(DataProvider, {
  things: {
    list: (params) =>
      Effect.succeed(mockThings.filter((t) => t.type === params.type)),

    create: (input) =>
      Effect.sync(() => {
        const id = `thing_${Date.now()}`;
        mockThings.push({ _id: id, ...input });
        return id;
      }),

    get: (id) => Effect.succeed(mockThings.find((t) => t._id === id)!),
  },

  connections: {
    create: (input) =>
      Effect.sync(() => {
        const id = `conn_${Date.now()}`;
        mockConnections.push({ _id: id, ...input });
        return id;
      }),

    list: (params) =>
      Effect.succeed(
        mockConnections.filter(
          (c) => c.relationshipType === params.relationshipType,
        ),
      ),

    delete: (id) =>
      Effect.sync(() => {
        const index = mockConnections.findIndex((c) => c._id === id);
        mockConnections.splice(index, 1);
      }),
  },

  events: {
    log: (event) =>
      Effect.sync(() => {
        mockEvents.push(event);
      }),
  },
});

describe("AuthService", () => {
  it("signs up a new user", async () => {
    const program = Effect.gen(function* () {
      const auth = yield* AuthService;

      const user = yield* auth.signUp({
        email: "test@example.com",
        password: "password123",
        name: "Test User",
        organizationId: "org_123",
      });

      return user;
    });

    const user = await Effect.runPromise(
      program.pipe(
        Effect.provide(AuthService.Default),
        Effect.provide(MockDataProvider),
      ),
    );

    expect(user.email).toBe("test@example.com");
    expect(user.name).toBe("Test User");
    expect(mockThings.length).toBe(1);
    expect(mockEvents.length).toBe(1); // sign_up event logged
  });

  it("signs in existing user", async () => {
    // Pre-populate mock user
    mockThings.push({
      _id: "user_1",
      type: "person",
      name: "Test User",
      properties: {
        email: "test@example.com",
        password: "hashed_password123",
        emailVerified: true,
      },
    });

    const program = Effect.gen(function* () {
      const auth = yield* AuthService;

      const result = yield* auth.signIn({
        email: "test@example.com",
        password: "password123",
        organizationId: "org_123",
      });

      return result;
    });

    const result = await Effect.runPromise(
      program.pipe(
        Effect.provide(AuthService.Default),
        Effect.provide(MockDataProvider),
      ),
    );

    expect(result.user.email).toBe("test@example.com");
    expect(result.session.userId).toBe("user_1");
    expect(mockConnections.length).toBe(1); // session connection created
    expect(mockEvents.length).toBe(1); // sign_in event logged
  });
});
```

**Key Point:** No mocking libraries needed! Effect.ts test layers provide real implementations.

---

## Migration Strategy

### Phase 1: Add AuthService Alongside Better Auth

Keep existing Better Auth, add AuthService:

```typescript
// frontend/src/auth/config.ts

// OLD: Better Auth (keep for now)
export const betterAuth = betterAuth({
  database: drizzleAdapter(db),
});

// NEW: AuthService (add alongside)
import { AuthService } from "@/services/AuthService";
import { ClientLayer } from "@/services/ClientLayer";

// Use both in parallel during migration
```

### Phase 2: Migrate Components One at a Time

```tsx
// OLD: Better Auth
import { useSession } from "@/auth/client";
const session = useSession();

// NEW: AuthService + Effect.ts
import { useEffectRunner } from "@/hooks/useEffectRunner";
import { AuthService } from "@/services/AuthService";
const { run } = useEffectRunner();
const program = Effect.gen(function* () {
  const auth = yield* AuthService;
  return yield* auth.getSession(token);
});
const session = await run(program);
```

### Phase 3: Remove Better Auth

Once all components migrated, remove Better Auth dependencies.

---

## Summary

### What We Achieve

✅ **Users = Things (type: person)**

- Works with all thing operations
- Can have connections (relationships, org membership)
- Can be actors in events (audit trail)
- Can be embedded in knowledge (AI context)

✅ **Sessions = Connections (type: session)**

- Real-time sync → revoke everywhere instantly
- Automatic expiration → query only active
- Analytics → count sessions, track patterns

✅ **Auth Events Logged**

- Complete audit trail
- Security analytics
- User timeline

✅ **Effect.ts Integration**

- Type-safe auth operations
- Composable workflows
- Consistent error handling
- Easy testing with test layers

✅ **Backend-Agnostic**

- AuthService uses DataProvider
- Works with Convex, Supabase, WordPress, Notion
- Swap backends by changing ONE line
- Same auth code, different backend

✅ **Multi-Tenant by Default**

- Users scoped to organizations
- Sessions scoped to organizations
- Data isolation automatic

### File Structure

```
frontend/src/
├── services/
│   ├── AuthService.ts             # Effect.ts auth service (uses DataProvider)
│   ├── ThingService.ts            # Generic thing operations
│   ├── ConnectionService.ts       # Generic connection operations
│   └── ClientLayer.ts             # Dependency injection
├── providers/
│   ├── DataProvider.ts            # Universal interface
│   ├── convex/
│   │   └── ConvexProvider.ts      # Convex implementation
│   ├── supabase/
│   │   └── SupabaseProvider.ts    # Supabase implementation
│   └── wordpress/
│       └── WordPressProvider.ts   # WordPress implementation
├── components/
│   └── auth/
│       ├── SignUpForm.tsx         # Uses AuthService + useEffectRunner
│       ├── SignInForm.tsx         # Uses AuthService + useEffectRunner
│       └── OAuthButton.tsx        # Uses AuthService + useEffectRunner
├── hooks/
│   └── useEffectRunner.ts         # Run Effect programs in React
└── middleware.ts                  # Session verification
```

### Key Principles

1. **6-Dimension Ontology:** All auth data fits the complete reality model
   - Organizations → multi-tenant isolation
   - People → users with roles and authorization
   - Things → optional device tracking
   - Connections → sessions & OAuth links
   - Events → complete audit trail
   - Knowledge → AI-powered security

2. **Effect.ts Everywhere:** Type-safe, composable auth operations

3. **Backend-Agnostic:** DataProvider pattern works with any backend

4. **Multi-Tenant by Design:** Every operation scoped to groupId

5. **Event-Driven:** All auth actions logged with actorId

6. **Real-Time:** Sessions as connections → instant updates everywhere

### Comparison

| Aspect             | Better Auth (Traditional)  | ONE 6-Dimension                    |
| ------------------ | -------------------------- | ---------------------------------- |
| **Architecture**   | 4 separate tables          | 6 dimensions                       |
| **Multi-Tenancy**  | Manual implementation      | Organizations (dimension 1)        |
| **Authorization**  | Basic roles                | People with roles (dimension 2)    |
| **Users**          | Separate `user` table      | Things (type: creator)             |
| **Devices**        | Not tracked                | Things (type: device) - optional   |
| **Sessions**       | Separate `session` table   | Connections (type: session)        |
| **OAuth**          | Separate `account` table   | Connections (type: oauth_account)  |
| **Audit Trail**    | Not logged                 | Events (dimension 5) - complete    |
| **Security AI**    | None                       | Knowledge (dimension 6) - patterns |
| **Backend**        | Database-specific adapters | Universal DataProvider             |
| **Error Handling** | Try/catch                  | Typed Effect errors                |
| **Testing**        | Mock database              | Effect test layers                 |
| **Data Isolation** | Manual queries             | Automatic (groupId scoping)        |
| **Analytics**      | External tool              | Built-in (events + knowledge)      |

### Next Steps

1. **Implement AuthService** with Effect.ts + DataProvider
2. **Create auth components** using useEffectRunner
3. **Add session middleware** for protected routes
4. **Test with mock DataProvider** (no database needed!)
5. **Deploy** with chosen backend (Convex, Supabase, etc.)

---

**Better Auth + ONE 6-Dimension Ontology = Universal, Type-Safe, Multi-Tenant Authentication**

Organizations partition. People authorize. Things exist. Connections relate. Events record. Knowledge understands. Effect.ts everywhere. Works with any backend.