Skip to main content

Token Manager System Design

This system implements a resilient Strategy Pattern wrapped in a Facade. It ensures that the application controllers remain agnostic to where tokens are stored (Browser Cookies vs. SQL Database) and how they are secured (Raw JWT vs. Encrypted Blob).

High-Level Architecture

The system uses a strict "Black Box" approach. External packages cannot access internal strategies directly; they must go through the public Manager interface.

Core Design Patterns

1. The Facade (Manager)

The rest of the application interacts only with the Manager interface. This provides a unified API for all token operations.

type Manager interface {
Set(c *gin.Context, t TokenType, value string, opts ...Option) error
Get(c *gin.Context, t TokenType, opts ...Option) (string, error)
DeleteMulti(c *gin.Context, tokens []TokenType, opts ...Option) error
}

2. The Strategy Pattern

Internal logic changes based on the TokenType.

  • Access Token: Uses cookieStrategy. Stores raw JWTs in stateless, secure, HTTP-only cookies.
  • Refresh Token: Uses dbStrategy. Encrypts the token and stores it in the database.

3. The Adapter Pattern

To prevent the Token Manager from depending on specific Database Structs (like User), we use Functional Adapters. This bridges the generic (id, string) requirement of the token package to the specific SQL schema of the application.

Token Types & Lifecycle

Token TypeStorageSecurityLifecycle
TypeAccessBrowser CookieRaw Signed JWTShort-lived (1h). Rotates automatically via middleware.
TypeRefreshSQL DatabaseEncrypted (AES)Long-lived (90d). Used for WorkOS/App sessions.
TypeGoogleRefreshSQL DatabaseEncrypted (AES)Long-lived. Used for background Calendar sync.
TypeAllRefreshVirtual (SQL)N/ADelete Only. Atomic cleanup of all DB sessions.

Optimized Logout Flow

When a user logs out, we need to clear the browser cookie AND remove the WorkOS session from the database, but we might want to keep the Google Calendar token alive for background jobs (or delete that too).

To avoid multiple database calls, we use DeleteMulti with the virtual TypeAllRefresh strategy.

Integration Example

The system is wired in app.go. This is where the specific SQL queries are injected into the generic manager.

// Wire the specific SQL queries to the generic Manager
workOSAdapter := token.NewRepoAdapter(
// Save
func(ctx context.Context, uid, val string) error {
return userRepo.UpdateColumn(ctx, uid, "workos_token", val)
},
// Get
func(ctx context.Context, uid string) (string, error) {
return userRepo.GetColumn(ctx, uid, "workos_token")
},
// Delete
func(ctx context.Context, uid string) error {
return userRepo.UpdateColumn(ctx, uid, "workos_token", "")
}
)

cfg := token.Config{
MainRefreshRepo: workOSAdapter,
// ... keys and domain settings
}

manager, _ := token.NewManager(cfg)