Skip to content

[v4] hasUpstreamAuthAssertion checks wrong Locals key — blocks all requests #345

@gandalf-at-lerian

Description

@gandalf-at-lerian

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 the auth. 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:

  1. lib-commons v4 assumes a Locals key that nobody sets
  2. 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 claims

Benefits 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions