Many systems start with a unified users table containing both authentication credentials (passwords, social provider IDs) and membership data (tiers, points, profile metadata). While simple, this creates a tight coupling that makes scaling, security audits, and switching auth providers difficult.
By separating these domains into an Identity Bridge (for authentication and token verification) and a Core Membership Service (for user profiles and system permissions), you create a modular, resilient security architecture.
1. Decoupling the Database Layers
The Identity Bridge manages credentials, MFA settings, and OAuth redirects. It stores only a mapping between the login credentials and an internal Account UUID. The Core Membership Service contains the profile data, billing details, and application state keyed by the same Account UUID. This separation offers significant benefits:
- Security: The membership database contains no passwords or credentials, reducing risk in the event of a database breach.
- Flexibility: You can completely replace Auth0 with Cognito, or migrate to a new SSO provider, without modifying any table schemas in the core membership system.
2. The Synchronizer Pattern
When a user signs up via the identity system, the Identity Bridge emits a UserCreated event. The Core Membership Service listens to this event to provision a new user profile with default roles and settings:
type UserCreatedPayload struct {
AccountID string json:"account_id"
Email string json:"email"
Locale string json:"locale"
}
func HandleUserCreated(payload UserCreatedPayload) error {
// Provision empty profile, assign default points, create audit log
return db.InsertProfile(payload.AccountID, payload.Email)
}
This decoupled pattern keeps security code isolated and allows membership features to scale independently.