Better Auth Plugin Integration

Overview

The Go backend is a read-only consumer of identity and organization data managed by the Bun monolith via Better Auth. This document explains the technical implementation of this integration, ensuring type-safe and performant access to the shared PostgreSQL schema.

1. Shared PostgreSQL Strategy

Go microservices skip the synchronization layer (NATS sync) and query the Better Auth tables directly. To maintain compatibility, Go models must exactly match the schema generated by the Better Auth plugins.

Standardized Table Names (Plural)

  • users: Managed by Better Auth core + Admin plugin.
  • organizations: Managed by Organization plugin.
  • members: Managed by Organization plugin (User-Org join table).
  • accounts: Managed by Better Auth core (OAuth/Social).

2. Admin Plugin Integration (Platform Roles)

The Admin Plugin on the Bun backend adds a role field to the users table. The Go backend resolves this role during JWT validation or through high-performance database lookups.

Role Extraction

The PermissionService resolves platform permissions by querying the role column in the users table:
  • user: Basic access.
  • admin / superadmin: Elevated system-wide access.

Read-Only Repository Pattern

The UserRepository in Go is restricted to SELECT operations. Mutations (e.g., changing a platform role) must be performed via the Bun backend’s Better Auth API.
// internal/modules/user/adapter/outbound/persistence/postgresql/user_repository.go
func (r *UserRepository) FindByID(ctx context.Context, id string) (*domain.User, error) {
    // SELECT * FROM users WHERE id = $1
}

3. Organization Plugin Integration (Multi-Tenancy)

The Organization Plugin manages multi-tenant boundaries. The Go backend leverages the members and organizations tables to enforce tenant isolation.

Member Roles

The members table contains the user-to-organization mapping with a role field:
  • owner / admin: Full organization management.
  • staff: Operational access (e.g., managing leave).
  • viewer: Read-only observation.

Tenant Isolation

Authorization logic in Go ensures that the organizationId from the user’s JWT matches the data context.
// Direct query to verify membership
err := db.Table("members").
    Where("user_id = ? AND organization_id = ?", userID, orgID).
    Select("role").
    Scan(&role).Error

4. Permission Resolution Flow

Low-latency authorization is achieved through a multi-tiered lookup:
  1. JWT Claims: Fast check of the orgRole and organizationId claims.
  2. Redis Cache: If mapping logic changes or claims are missing, query Redis (5m TTL).
  3. PostgreSQL: On cache miss, perform a direct indexed query on the members table.

5. Consistency via NATS

Since Go is read-only, it must be notified when a user’s role or membership changes in the Bun backend. We use NATS for real-time cache invalidation:
  • Subject: member.role.changed, member.removed
  • Action: The Go MemberRoleChangeConsumer invalidates the local Redis cache key perm:<userId>:<orgId>.
  • Result: The next Go request triggers a fresh database query, ensuring sub-second consistency across microservices.

6. Performance Benchmarks

To ensure high-performance lookups, the following indexes are strictly maintained:
  • idx_users_role
  • idx_members_user_org (Compound index for membership checks)
  • idx_members_organization_id_role (For listing org members)