Tencent/WeKnora · error

workspace mismatch: got %d want %d

Error message

workspace mismatch: got %d want %d

What it means

verifyExternalUserJWT in internal/middleware/auth.go:649 rejects tokens whose tenant/workspace claim does not match the tenant resolved for the request. principalTenantIDFromClaims extracts the workspace id from the claims; if it differs from the request's tenant ID the token is refused, preventing cross-workspace token reuse.

Source

Thrown at internal/middleware/auth.go:649

	})
	if err != nil {
		return "", err
	}
	if token == nil || !token.Valid {
		return "", errors.New("invalid external user token")
	}
	exp, err := claims.GetExpirationTime()
	if err != nil || exp == nil {
		return "", errors.New("missing expiration")
	}
	if time.Until(exp.Time) > maxExternalUserTokenTTL {
		return "", fmt.Errorf("token lifetime exceeds %s", maxExternalUserTokenTTL)
	}
	if nbf, nbfErr := claims.GetNotBefore(); nbfErr == nil && nbf != nil && time.Now().Before(nbf.Time) {
		return "", errors.New("token not yet valid")
	}
	if got := principalTenantIDFromClaims(claims); got != tenantID {
		return "", fmt.Errorf("workspace mismatch: got %d want %d", got, tenantID)
	}
	sub, _ := claims["sub"].(string)
	sub = strings.TrimSpace(sub)
	if sub == "" {
		return "", errors.New("missing subject")
	}
	return sub, nil
}

func validateExternalUserID(id string) error {
	id = strings.TrimSpace(id)
	if id == "" {
		return errors.New("empty external user id")
	}
	if len(id) > maxExternalUserIDLen {
		return fmt.Errorf("external user id too long (max %d)", maxExternalUserIDLen)
	}
	for _, r := range id {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Issue a token per workspace with the correct tenant/workspace claim.
  2. Re-mint tokens after a workspace is recreated or its ID changes.
  3. Verify the client is pointed at the intended tenant's API endpoint.

Example fix

// before
claims := jwt.MapClaims{"aud": "weknora", "sub": uid} // no/mismatched workspace claim
// after
claims := jwt.MapClaims{"aud": "weknora", "sub": uid, "workspace_id": tenantID}
Defensive patterns

Strategy: validation

Validate before calling

if got := workspaceIDFromClaims(claims); got != requestTenantID { // mint token for the right workspace
}

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "workspace mismatch") {
        // obtain a token bound to the current tenant/workspace id
    }
}

Prevention

When it happens

Trigger: A correctly signed token whose workspace/tenant claim is for tenant A is presented on a request authenticated as tenant B; principalTenantIDFromClaims returns a value != tenantID.

Common situations: Reusing one token across multiple workspaces; tenant ID changed (e.g. recreated workspace with new ID) but cached tokens still carry the old ID; client misconfigured with another workspace's token endpoint.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/0925509704d8d084. Report an issue: GitHub.