Skip to content

Authorization Architecture

Purpose

This document defines the authorization strategy for the Farmer1st platform, managing complex relationships between users, entities, and data access across a multi-tenant ecosystem.

Current State

Authorization Model

┌─────────────────────────────────────────────────────────────────────────────┐
│                      AUTHENTICATION vs AUTHORIZATION                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────┐    ┌─────────────────────────────────┐   │
│   │        SUPERTOKENS          │    │           OPENFGA               │   │
│   │       (Authentication)      │    │        (Authorization)          │   │
│   │                             │    │                                 │   │
│   │   "Who are you?"            │    │   "What can you access?"        │   │
│   │                             │    │                                 │   │
│   │   • Login / Logout          │    │   • Relationship-based access   │   │
│   │   • Session management      │    │   • Role permissions            │   │
│   │   • JWT issuance            │    │   • Entity hierarchies          │   │
│   │   • Identity verification   │    │   • Data ownership              │   │
│   │                             │    │                                 │   │
│   └─────────────────────────────┘    └─────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Technology Choice

Component Technology Deployment
Authorization Engine OpenFGA Self-managed on EKS
Tuple Storage PostgreSQL Dedicated instance (not shared with app DB)

Decisions and Rationale

Decision Rationale
OpenFGA for authorization Designed for complex relationship-based access control (ReBAC), handles graph traversals, proven at scale (Okta/Auth0)
Self-managed Consistent with SuperTokens/Unleash pattern, cost control at scale
Separate from SuperTokens Clear separation of concerns: authn vs authz
Dedicated PostgreSQL Isolate authz workload, independent scaling, no contention with app data

Relationship Model

The Challenge

Farmer1st has complex, multi-layered relationships:

┌─────────────────────────────────────────────────────────────────────────────┐
│                     RELATIONSHIP COMPLEXITY                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   USER TYPES              ENTITIES              RELATIONSHIPS               │
│   ──────────              ────────              ─────────────               │
│   • Farmer                • Farm                • User owns Entity          │
│   • Coop Member           • Family              • User is member of Entity  │
│   • Coop Admin            • Cooperative         • Entity relates to Entity  │
│   • Agent                 • Factory             • User has role on Entity   │
│   • Brand Employee        • Brand               • Access via chain          │
│   • Factory Manager       • (more TBD...)       • Direct or indirect        │
│   • (more TBD...)                                                           │
│                                                                             │
│   EXAMPLE ACCESS PATHS                                                      │
│   ────────────────────                                                      │
│                                                                             │
│   Farmer ──owns──► Farm                                                     │
│                                                                             │
│   Farmer ──member_of──► Cooperative ──supplies──► Nestlé                   │
│                                                                             │
│   Nestlé Employee ──works_for──► Nestlé ──sources_from──► Coop ──► Farm    │
│                                                                             │
│   Agent ──manages──► [Farm1, Farm2, Farm3...]                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

OpenFGA Authorization Model

# Conceptual OpenFGA model (DSL)

type user

type farm
  relations
    define owner: [user]
    define manager: [user]
    define viewer: [user, cooperative#member, brand#employee]
    define can_view: owner or manager or viewer
    define can_edit: owner or manager

type family
  relations
    define head: [user]
    define member: [user]

type cooperative
  relations
    define admin: [user]
    define member: [user]
    define viewer: [user, brand#employee]
    define can_view: admin or member or viewer
    define can_edit: admin

type brand
  relations
    define admin: [user]
    define employee: [user]
    define sources_from: [cooperative, farm]
    define can_view_supplier: admin or employee

type factory
  relations
    define manager: [user]
    define worker: [user]
    define owned_by: [brand]

Access Check Examples

Question OpenFGA Check
Can user X view farm Y? Check(user:X, can_view, farm:Y)
Can user X edit coop Z? Check(user:X, can_edit, cooperative:Z)
What farms can user X view? ListObjects(user:X, can_view, farm)
Who can view farm Y? ListUsers(farm:Y, can_view)

Indirect Access (Graph Traversal)

OpenFGA handles transitive relationships:

# Nestlé employee accessing farm data through coop relationship

Tuples:
  user:alice  │ employee    │ brand:nestle
  brand:nestle│ sources_from│ cooperative:coop1
  cooperative:coop1 │ member │ user:farmer_bob
  user:farmer_bob │ owner   │ farm:farm123

Query: Can user:alice view farm:farm123?
        ↓
OpenFGA traverses: alice → nestle → coop1 → farm123
        ↓
Result: ✅ Yes (via brand employee → sources_from → cooperative → farm)

Integration Pattern

Authorization Check Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                        AUTHORIZATION FLOW                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   1. Request arrives (JWT validated by Cloudflare)                          │
│      │                                                                      │
│      ▼                                                                      │
│   2. API extracts user ID from JWT                                          │
│      │                                                                      │
│      ▼                                                                      │
│   3. API calls OpenFGA: Check(user:X, action, resource:Y)                   │
│      │                                                                      │
│      ├── Denied → 403 Forbidden                                             │
│      │                                                                      │
│      └── Allowed → Continue to business logic                               │
│          │                                                                  │
│          ▼                                                                  │
│   4. Execute operation, return response                                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Tuple Management

Tuples (relationships) are created/deleted when:

Event Tuple Action
Farmer registers and creates farm user:X, owner, farm:Y
Farmer joins cooperative cooperative:Z, member, user:X
Coop signs contract with brand brand:A, sources_from, cooperative:Z
Employee added to brand user:B, employee, brand:A
User removed from coop Delete cooperative:Z, member, user:X

Tuple changes are event-driven — published to Kafka, processed by workers that update OpenFGA.

Scaling Considerations

Expected Scale

Metric Estimate Notes
Users 100M Mostly farmers
Relationships per user 5-10 avg Farm, family, coop memberships
Total tuples 500M - 1B Users + entity-to-entity relationships
Authz checks/sec TBD Most API calls require a check

Infrastructure Planning

Component Recommendation
OpenFGA instances Multiple replicas on EKS
PostgreSQL (tuples) Dedicated RDS instance, not shared with app
Read replicas Consider for high-read authorization patterns
Caching Redis for frequent authz checks (short TTL)
Latency target <10ms for authorization checks

Performance Optimizations

  1. Batch checks: Use BatchCheck for multiple resources
  2. List objects: Use ListObjects instead of N individual checks
  3. Caching: Cache positive authz results briefly (30s-60s)
  4. Denormalization: For very hot paths, consider denormalizing permissions

Trade-offs Considered

OpenFGA vs OPA (Open Policy Agent)

Factor OpenFGA OPA
Model Relationship-based (ReBAC) Policy-based (Rego)
Graph queries ✅ Native ❌ Must implement
Complex hierarchies ✅ Designed for it ⚠️ Possible but harder
Learning curve ✅ Simpler DSL ❌ Rego is complex
Use case fit ✅ "Who can access what" Better for "what rules apply"

Decision: OpenFGA is purpose-built for our relationship-heavy model.

OpenFGA vs Custom Solution

Factor OpenFGA Custom
Development time ✅ Ready to use ❌ Months of work
Graph traversal ✅ Built-in ❌ Must build
Proven at scale ✅ Okta/Auth0 ❌ Unproven
Flexibility ⚠️ DSL constraints ✅ Anything goes

Decision: Don't reinvent the wheel.

Open Questions

  • Exact entity types and relationships (see 09-domain-model.md)
  • Caching strategy for authz checks (Redis TTL?)
  • Audit logging for authorization decisions?
  • How to handle relationship changes (eventual consistency?)
  • OpenFGA version and upgrade strategy?

Dependencies

  • SuperTokens for authentication (provides user ID)
  • Kafka for event-driven tuple updates
  • PostgreSQL (dedicated instance) for tuple storage
  • Redis for authz caching (optional optimization)

Last Updated: 2025-12-25