Swift: Uploading Data Securely to a MySQL Database

Problem: Upload ResearchKit derived data securely to a MySQL database

Data derived from a consent form, survey forms and/or active tasks are secure while stored on the iPhone but transferring the data to a 'secure' server is the challenge. While there are a number of reports and examples for retrieving web data onto the iPhone there are limited examples for the reverse, i.e. this problem.

Answer: POST data via HTTPS to a Webserver using Swift and php

Data was POSTed to a webserver using URLSession.shared.uploadTask. These data are received by a php page on the server which inserts the data into a MySQL table.

The pieces:

  1. Swift version 3.0
  2. Xcode version 8.1
  3. MAMP (for testing ) v4.0.5
  4. An App for the iPhone to gather and post the data
  5. A php webpage to receive the data and insert it into the database
  6. A MySQL database to receive, store and retrieve the data
  7. A password protected php webpage to display the stored data

Starting with the database

For this example a MySQL database was developed using MAMP (https://www.mamp.info/en/). First a user was created in this example with all privileges. This can be used during development but privileges should be reduced to the minimum required on the production server. This user is named 'db_user'.

User set-up

The next step is to create a new database, db_example, and a table, example_table.

New database with table

This table has an id field and five CHAR fields. Other than the id field these could be any SQL table fields.

The iOS app

Start a new project as an iOS, single window, Swift application. Since you will be sending data using https you shouldn't need to add an entry to the Info.plist but in case you get an error to you an add an App Transport Security Setting entry with Allow Arbitrary Loads = YES

Info.plist settings

Create a Main.storyboard as you need to collect the data stored. This is just a demo app so there are just five input, textfields and labels with one button. You may be gathering the data is some other more elaborate method.

The main.storyboard

Link up the textfields as outlets and the button as an action in ViewController.swift.

import UIKit

class ViewController: UIViewController
{

// The textfield outlets
    @IBOutlet weak var item1: UITextField!
    @IBOutlet weak var item2: UITextField!
    @IBOutlet weak var item3: UITextField!
    @IBOutlet weak var item4: UITextField!
    @IBOutlet weak var item5: UITextField!

// the button action function    
    @IBAction func uploadData(_ sender: Any)
    {
        let url = NSURL(string: "http://localhost:8888/sdb/receive.php") // locahost MAMP - change to point to your database server
        
        var request = URLRequest(url: url as! URL)
        request.httpMethod = "POST"
        
        var dataString = "secretWord=44fdcv8jf3" // starting POST string with a secretWord
        
// the POST string has entries separated by &

        dataString = dataString + "&item1=\(item1.text!)" // add items as name and value
        dataString = dataString + "&item2=\(item2.text!)"
        dataString = dataString + "&item3=\(item3.text!)"
        dataString = dataString + "&item4=\(item4.text!)"
        dataString = dataString + "&item5=\(item5.text!)"
        
// convert the post string to utf8 format
        
        let dataD = dataString.data(using: .utf8) // convert to utf8 string
        
        do
        {
        
// the upload task, uploadJob, is defined here

            let uploadJob = URLSession.shared.uploadTask(with: request, from: dataD)
            {
                data, response, error in
                
                if error != nil {
                    
// display an alert if there is an error inside the DispatchQueue.main.async

                    DispatchQueue.main.async
                    {
                            let alert = UIAlertController(title: "Upload Didn't Work?", message: "Looks like the connection to the server didn't work.  Do you have Internet access?", preferredStyle: .alert)
                            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
                            self.present(alert, animated: true, completion: nil)
                    }
                }
                else
                {
                    if let unwrappedData = data {
                        
                        let returnedData = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue) // Response from web server hosting the database
                        
                        if returnedData == "1" // insert into database worked
                        {

// display an alert if no error and database insert worked (return = 1) inside the DispatchQueue.main.async

                            DispatchQueue.main.async
                            {
                                let alert = UIAlertController(title: "Upload OK?", message: "Looks like the upload and insert into the database worked.", preferredStyle: .alert)
                                alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
                                self.present(alert, animated: true, completion: nil)
                            }
                        }
                        else
                        {
// display an alert if an error and database insert didn't worked (return != 1) inside the DispatchQueue.main.async

                            DispatchQueue.main.async
                            {

                            let alert = UIAlertController(title: "Upload Didn't Work", message: "Looks like the insert into the database did not worked.", preferredStyle: .alert)
                            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
                            self.present(alert, animated: true, completion: nil)
                            }
                        }
                    }
                }
            }
            uploadJob.resume()
        }
    }
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

The receive.php file

The last part is the pasword protected php file on the webserver to receive the data from the POST and insert it into the database.
<?php
	$secret = $_POST["secretWord"];
	if ("44fdcv8jf3" != $secret) exit; // note the same secret as the app
	
	$item1 = $_POST['item1'];
	$item2 = $_POST['item2'];
	$item3 = $_POST['item3'];
	$item4 = $_POST['item4'];
	$item5 = $_POST['item5'];

// Create connection
	$mysqli=mysqli_connect("127.0.0.1","db_user","password","db_example"); // localhost, user name, user password, database name
 
// Check connection
	if (mysqli_connect_errno())
	{
	  echo "

Failed to connect to MySQL: " . mysqli_connect_error(); } $query = "insert into `example_table` (item1, item2, item3, item4, item5) value ('".$item1."','".$item2."','".$item3."','".$item4."','".$item5."')"; $result = mysqli_query($mysqli,$query); echo $result; // sends 1 if insert worked ?>


Comments, Corrections, Suggestions: David Bourne (david@boomer.org)