Overview
The Go backend uses a stateless JWT authentication system combined with direct PostgreSQL queries to Better Auth tables. This architecture decouples identity management (handled by Better Auth on the Bun monolith) from resource authorization checks (handled by Go microservices). Key principle: Go backend is read-only with respect to user and organization data. All mutations (create user, assign roles, etc.) are performed via the Better Auth API on the Bun backend.
Core Components
1. Better Auth (Bun Monolith)
The source of truth for authentication and authorization. It manages:- User registration, login, OAuth, email verification
- Platform-level roles (via admin plugin):
user,admin,superadmin - Organization membership and roles (via organization plugin):
owner,admin,staff,viewer - Issues signed JWTs containing:
userId(fromusers.id)emailrole(platform role fromusers.role)organizationId(user’s active organization)orgRole(user’s role in that organization)isActive(derived fromusers.banned)
users— User identities and platform rolesaccounts— OAuth provider data and password hashessessions— Active user sessionsmembers— Organization memberships and per-org rolesorganizations— Organization entitiesinvitations— Pending organization invitesjwks— JWT key sets for signing
2. JWT Validation & Middleware
The Go backend validates incoming JWTs on every protected request:- Extraction: Reads
Authorization: Bearer <token>header - Signature Verification: HS256 using shared
BETTER_AUTH_SECRET(32+ chars, configured in both Bun and Go) - Claims Extraction: Parses userId, email, role, organizationId, orgRole from payload
- Context Injection: Stores user data in Echo context for use in handlers
3. Redis Caching Strategy
Permission Cache (Organization-Level)
- Key:
perm:<userId>:<organizationId> - Value:
{ "role": "owner|admin|staff|viewer", "permissions": ["data:read", "data:write", "leave:approve", ...] } - TTL: 5 minutes (fallback)
- Purpose: Cache role-to-permission resolution from the
memberstable. Avoids repeated DB queries for authorization checks in every request. - Invalidation:
- Real-time: When a user’s role or membership changes via the Better Auth API on Bun, an event is published to NATS. The Go backend’s
MemberRoleChangeConsumerlistens for these events and invalidates the corresponding cache keys immediately. - Time-based: 5-minute TTL as a safe fallback for consistency.
- Real-time: When a user’s role or membership changes via the Better Auth API on Bun, an event is published to NATS. The Go backend’s
4. Direct Database Queries (No User Sync)
The architectural decision to eliminate data synchronization between microservices in favor of direct queries to a single source of truth (Better Auth PostgreSQL tables). Current approach:- Go backend queries
users,members, andorganizationstables directly. - No data duplication — single source of truth is Better Auth’s schema.
- User lookups (repositories) now read from Better Auth tables.
- Org role queries (permission service) read directly from
memberstable.
- Absolute Consistency: No synchronization lag or event processing delays.
- Simplicity: Reduced infrastructure complexity (no NATS user sync topic).
- Reduced Latency: Database joins are generally faster than distributed event processing and local duplication.
Related Files
Authentication
- Middleware:
internal/api/http/middleware/auth_middleware.go— JWT extraction and validation - Validator:
internal/api/http/middleware/better_auth_validator.go— Signature verification with Redis caching
User & Organization Data (Read-Only)
-
User Repository:
internal/modules/user/adapter/outbound/persistence/postgresql/user_repository.goFindByID()— Queriesuserstable by IDFindByEmail()— Queriesuserstable by email- No write operations; all mutations happen via Better Auth API
-
Organization Repository:
internal/modules/organizations/adapter/outbound/persistence/postgresql/organization_repository.goGetByID()— QueriesorganizationstableGetBySlug()— Queriesorganizationstable by slugListByUserID()— Joinsmembersandorganizationstables- No write operations
Authorization
- Checks user permissions based on organization role
- Caches permissions using Redis with 5-minute TTL
- Queries
memberstable for org roles
Event Consumers
- Member Role Change Consumer:
internal/modules/organizations/adapter/inbound/messaging/member_role_change_consumer.go- Listens for member role changes, additions, and removals via NATS
- Invalidates permission cache in real-time