cockpit_auth_login_async (CockpitAuth *self,
                          const gchar *path,
                          GIOStream *connection,
                          GHashTable *headers,
                          GAsyncReadyCallback callback,
                          gpointer user_data)
{
  g_autoptr(GSimpleAsyncResult) result = NULL;
  CockpitSession *session;
  GError *error = NULL;
  g_autofree gchar *type = NULL;
  g_autofree gchar *conversation = NULL;
  g_autofree gchar *authorization = NULL;
  g_autofree gchar *application = NULL;

  g_return_if_fail (path != NULL);
  g_return_if_fail (headers != NULL);

  self->startups++;

  result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
                                      cockpit_auth_login_async);

  if (!can_start_auth (self))
    {
      g_message ("Request dropped; too many startup connections: %u", self->startups);
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_FAILED,
                                       "Connection closed by host");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  application = cockpit_auth_parse_application (path, NULL);

  /* If the client sends a TLS certificate to cockpit-tls, treat this as a
   * definitive login type, and don't just silently fall back to other types */
  if (https_instance_has_certificate_file (NULL, 0) != -1)
    {
      g_debug ("TLS connection has peer certificate, using tls-cert auth type");
      type = g_strdup ("tls-cert");
      /* don't send any actual authorization here; we don't want to put any trust in data sent from cockpit-ws */
      authorization = g_strdup ("tls-cert");
    }
  else
    {
      g_debug ("No peer certificate");
      authorization = cockpit_auth_steal_authorization (headers, connection, &type, &conversation);

      if (!authorization)
        {
          g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                           "Authentication required");
          g_simple_async_result_complete_in_idle (result);
          goto out;
        }
    }

  if (!application)
    {
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                       "Application required");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  if (conversation)
    {
      session = g_hash_table_lookup (self->conversations, conversation);
      if (!session)
        {
          g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                           "Invalid conversation token");
          g_simple_async_result_complete_in_idle (result);
          goto out;
        }

      g_simple_async_result_set_op_res_gpointer (result, cockpit_session_ref (session), cockpit_session_unref);
    }
  else
    {
      session = cockpit_session_launch (self, connection, headers, type, authorization, application, &error);
      if (!session)
        {
          g_simple_async_result_take_error (result, error);
          g_simple_async_result_complete_in_idle (result);
          goto out;
        }
      g_simple_async_result_set_op_res_gpointer (result, session, cockpit_session_unref);
    }

  cockpit_session_reset (session);
  session->result = g_object_ref (result);

  session->authorization = authorization;
  authorization = NULL;

  if (conversation && !reply_authorize_challenge (session))
    {
      g_simple_async_result_set_error (result, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED,
                                       "Invalid conversation reply");
      g_simple_async_result_complete_in_idle (result);
      goto out;
    }

  reset_authorize_timeout (session, FALSE);

out:
  if (authorization)
    cockpit_memory_clear (authorization, -1);
}