How to combine several form objects into one?
I've created a working example of a combined form object.
Please read the comments below if you want to understand how it works and the workarounds that were applied.

The gist can be found here

Note: There may still be unforeseen issues with the solution below. 

&lt;?php

namespace Drupal\sandbox\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\Node;
use Drupal\user\Entity\User;

class ComboForm extends FormBase {

  /**
   * This constant is used as a key inside the main form state object to gather
   * all the inner form state objects.
   * @const
   * @see getInnerFormState()
   */
  const INNER_FORM_STATE_KEY = 'inner_form_state';

  const MAIN_SUBMIT_BUTTON = 'submit';

  /**
   * @var \Drupal\Core\Form\FormInterface[]
   */
  protected $innerForms = [];

  /**
   * The ComboForm constructor needs to initialize the inner form objects
   * that this form will later use.
   *
   * Because both the user and the node forms are entity forms then an entity
   * object needs to be assigned to them.
   *
   * Because the node entity has bundle, the bundle (type) must be defined
   * before the form is generated.
   *
   * @TODO If the node type is customizable then a custom controller is needed.
   *
   * @TODO should this form allow edits? If so then the form objects should
   *       be assigned with the edited entities.
   */
  public function __construct() {
    $this-&gt;innerForms['user'] = \Drupal::entityTypeManager()
      -&gt;getFormObject('user', 'default')
      -&gt;setEntity(User::create());

    $this-&gt;innerForms['node'] = \Drupal::entityTypeManager()
      -&gt;getFormObject('node', 'default')
      -&gt;setEntity(Node::create([
        'type' =&gt; 'article'
      ]));
  }

  /**
   * {@inheritDoc}
   */
  public function getFormId() {
    return implode('__', [
      'combo_form',
      $this-&gt;innerForms['user']-&gt;getFormId(),
      $this-&gt;innerForms['node']-&gt;getFormId()
    ]);
  }

  /**
   * The build form needs to take care of the following:
   *   - Creating a custom form state object for each inner form (and keep it
   *     inside the main form state.
   *   - Generating a render array for each inner form.
   *   - Handle compatibility issues such as #process array and action elements.
   *
   * {@inheritDoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    $form['#process'] = \Drupal::service('element_info')-&gt;getInfoProperty('form', '#process', []);
    $form['#process'][] = '::processForm';

    foreach ($this-&gt;innerForms as $key =&gt; $inner_form_object) {
      $inner_form_state = static::createInnerFormState($form_state, $inner_form_object, $key);


      // By placing the actual inner form inside a container element (such as
      // details) we gain the freedom to alter the wrapper of the inner form
      // with little damage to the render element attributes of the inner form.
      $inner_form = ['#parents' =&gt; [$key]];
      $inner_form = $inner_form_object-&gt;buildForm($inner_form, $inner_form_state);
      $form[$key] = [
        '#type' =&gt; 'details',
        '#title' =&gt; $this-&gt;t('Inner form: %key', ['%key' =&gt; $key]),
        'form' =&gt; $inner_form
      ];

      $form[$key]['form']['#type'] = 'container';
      $form[$key]['form']['#theme_wrappers'] = \Drupal::service('element_info')-&gt;getInfoProperty('container', '#theme_wrappers', []);
      unset($form[$key]['form']['form_token']);

      // The process array is called from the FormBuilder::doBuildForm method
      // with the form_state object assigned to the this (ComboForm) object.
      // This results in a compatibility issues because these methods should
      // be called on the inner forms (with their assigned FormStates).
      // To resolve this we move the process array in the inner_form_state
      // object.
      if (!empty($form[$key]['form']['#process'])) {
        $inner_form_state-&gt;set('#process', $form[$key]['form']['#process']);
        unset($form[$key]['form']['#process']);
      }
      else {
        $inner_form_state-&gt;set('#process', []);
      }

      // The actions array causes a UX problem because there should only be a
      // single save button and not multiple.
      // The current solution is to move the #submit callbacks of the submit
      // element to the inner form element root.
      if (!empty($form[$key]['form']['actions'])) {
        if (isset($form[$key]['form']['actions'][static::MAIN_SUBMIT_BUTTON])) {
          $form[$key]['form']['#submit'] = $form[$key]['form']['actions'][static::MAIN_SUBMIT_BUTTON]['#submit'];
        }

        unset($form[$key]['form']['actions']);
      }
    }

    // Default action elements.
    $form['actions'] = [
      '#type' =&gt; 'actions',
      static::MAIN_SUBMIT_BUTTON =&gt; [
        '#type' =&gt; 'submit',
        '#value' =&gt; $this-&gt;t('Save'),
        '#validate' =&gt; ['::validateForm'],
        '#submit' =&gt; ['::submitForm']
      ]
    ];

    return $form;
  }

  /**
   * This method will be called from FormBuilder::doBuildForm during the process
   * stage.
   * In here we call the #process callbacks that were previously removed.
   *
   * @param array $element
   *   The form element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param array $complete_form
   * @return array
   *   The altered form element.
   *
   * @see \Drupal\Core\Form\FormBuilder::doBuildForm()
   */
  public function processForm(array &amp;$element, FormStateInterface &amp;$form_state, array &amp;$complete_form) {
    foreach ($this-&gt;innerForms as $key =&gt; $inner_form) {
      $inner_form_state = static::getInnerFormState($form_state, $key);
      foreach ($inner_form_state-&gt;get('#process') as $callback) {
        // The callback format was copied from FormBuilder::doBuildForm().
        $element[$key]['form'] = call_user_func_array($inner_form_state-&gt;prepareCallback($callback), array(&amp;$element[$key]['form'], &amp;$inner_form_state, &amp;$complete_form));
      }
    }

    return $element;
  }

  /**
   * {@inheritDoc}
   */
  public function validateForm(array &amp;$form, FormStateInterface $form_state) {
    /** @var \Drupal\Core\Form\FormValidatorInterface $form_validator */
    $form_validator = \Drupal::service('form_validator');
    foreach ($this-&gt;innerForms as $form_key =&gt; $inner_form) {
      $inner_form_state = static::getInnerFormState($form_state, $form_key);

      // Pass through both the form elements validation and the form object
      // validation.
      $inner_form-&gt;validateForm($form[$form_key]['form'], $inner_form_state);
      $form_validator-&gt;validateForm($inner_form-&gt;getFormId(), $form[$form_key]['form'], $inner_form_state);


      foreach ($inner_form_state-&gt;getErrors() as $error_element_path =&gt; $error) {
        $form_state-&gt;setErrorByName($form_key . '][' . $error_element_path, $error);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public function submitForm(array &amp;$form, FormStateInterface $form_state) {
    /** @var \Drupal\Core\Form\FormSubmitterInterface $form_submitter */
    $form_submitter = \Drupal::service('form_submitter');

    foreach ($this-&gt;innerForms as $key =&gt; $inner_form) {
      $inner_form_state = static::getInnerFormState($form_state, $key);

      // The form state needs to be set as submitted before executing the
      // doSubmitForm method.
      $inner_form_state-&gt;setSubmitted();
      $form_submitter-&gt;doSubmitForm($form[$key]['form'], $inner_form_state);
    }
  }

  /**
   * Before returning the innerFormState object, we need to set the
   * complete_form, values and user_input properties from the main form state.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The main form state.
   * @param string $key
   *   The key used to store the inner form state.
   * @return \Drupal\Core\Form\FormStateInterface
   *   The inner form state.
   */
  protected static function getInnerFormState(FormStateInterface $form_state, $key) {
    /** @var \Drupal\Core\Form\FormStateInterface $inner_form_state */
    $inner_form_state = $form_state-&gt;get([static::INNER_FORM_STATE_KEY, $key]);
    $inner_form_state-&gt;setCompleteForm($form_state-&gt;getCompleteForm());
    $inner_form_state-&gt;setValues($form_state-&gt;getValues() ? : []);
    $inner_form_state-&gt;setUserInput($form_state-&gt;getUserInput() ? : []);
    return $inner_form_state;
  }

  /**
   * After the initialization of the inner form state, we need to assign it with
   * the inner form object and set it inside the main form state.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The main form state.
   * @param \Drupal\Core\Form\FormInterface $form_object
   *   The inner form object
   * @param string $key
   *   The key used to store the inner form state.
   * @return \Drupal\Core\Form\FormStateInterface
   *   The inner form state.
   */
  protected static function createInnerFormState(FormStateInterface $form_state, FormInterface $form_object, $key) {
    $inner_form_state = new FormState();
    $inner_form_state-&gt;setFormObject($form_object);
    $form_state-&gt;set([static::INNER_FORM_STATE_KEY, $key], $inner_form_state);
    return $inner_form_state;
  }

}