-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Context
In the v4 unification (PR #336), the TenantMiddleware and MultiPoolMiddleware added a security guard hasUpstreamAuthAssertion() that verifies upstream auth middleware has run before allowing ParseUnverified on the JWT token.
Where to change
1. The assertion function
File: commons/tenant-manager/middleware/tenant.go
Lines 213-226 (v4.0.0-beta.2):
func hasUpstreamAuthAssertion(c *fiber.Ctx) bool {
if c == nil {
return false
}
if userID, ok := c.Locals("user_id").(string); ok && userID != "" { // ← line 221: wrong key
return true
}
return false
}2. TenantMiddleware calls the assertion
File: commons/tenant-manager/middleware/tenant.go
Line 116: if !hasUpstreamAuthAssertion(c) {
3. MultiPoolMiddleware calls the assertion
File: commons/tenant-manager/middleware/multi_pool.go
Line 338: if !hasUpstreamAuthAssertion(c) {
(The function hasUpstreamAuthAssertion is defined in tenant.go and shared by both middlewares.)
Problem
The function checks c.Locals("user_id"), but no auth middleware in the Lerian stack sets this key:
- tenant-manager app (WorkOS auth): sets
c.Locals("auth.user_id")— note theauth.prefix. Does not match. - lib-auth (Casdoor, used by midaz/reporter/plugins): does not set any Locals at all. It makes an HTTP call to plugin-auth for authorization and returns next/forbidden without storing identity in Fiber locals.
Impact
The v4 tenant middleware returns 401 Unauthorized for 100% of requests in every service that uses TenantMiddleware or MultiPoolMiddleware, regardless of whether the JWT is valid.
Critical blocker for v4 adoption.
Related: lib-auth (LerianStudio/lib-auth)
The root cause is split across two repos:
- lib-commons v4 assumes a Locals key that nobody sets
- lib-auth (Casdoor middleware) doesn't set any identity in
c.Locals()after successful auth — it only returns next/forbidden with no trace in the Fiber context
The lib-auth middleware (auth/middleware/middleware.go) validates the token via HTTP call to plugin-auth, but never stores the authenticated user's identity in Fiber locals. This means no downstream middleware can verify that auth has already run.
Recommended Action Plan
Phase 1 — Immediate fix in lib-commons v4 (unblocks v4 migration)
Make the assertion key configurable via middleware option, defaulting to disabled (v3 behavior):
// Option for both TenantMiddleware and MultiPoolMiddleware
func WithAuthAssertionKey(key string) Option {
return func(m *middleware) {
m.authAssertionKey = key
}
}
func hasUpstreamAuthAssertion(c *fiber.Ctx, key string) bool {
if key == "" {
return true // no assertion configured, trust middleware ordering (v3 behavior)
}
if c == nil {
return false
}
if val, ok := c.Locals(key).(string); ok && val != "" {
return true
}
return false
}This way:
- Services using WorkOS (tenant-manager app) can set
WithAuthAssertionKey("auth.user_id") - Services using Casdoor (midaz, reporter, plugins) omit the option and get v3 behavior
- No service breaks on v4 migration
Phase 2 — Evolve lib-auth (LerianStudio/lib-auth)
Update the Casdoor auth middleware to set identity in Fiber locals after successful validation:
// In auth/middleware/middleware.go, after successful authorization:
c.Locals("user_id", sub) // or extract from JWT claimsBenefits beyond this issue:
- Enables audit logging with user identity
- Enables tracing with user context
- Enables any downstream middleware to verify auth ran
- Aligns with the security-in-depth pattern that v4 intends
Phase 3 — Enable assertion by default
Once lib-auth sets locals, services can opt-in to WithAuthAssertionKey("user_id") for defense-in-depth. Eventually this could become the default.
Raised by
Jefferson Rodrigues (CTO) and Gabriel Brecci during multi-tenant task force review.