Bart Simons

Bart Simons


Thoughts, stories and ideas.

Bart Simons
Author

Share


Tags


Twitter


Swift + Perfect + Authentication API Example

Bart SimonsBart Simons

So you want to build your next backend API in Swift? No problem! Perfect has got you covered. Perfect is a collection of libraries to turn your next server-side Swift application into reality. It is blazingly fast and awesome!

So I decided to dive a little bit deeper into the possibilities of Swift and Perfect, and so I quickly decided that an authentication API would be a 'perfect' idea (see what I did there?) to get started. So, what do we need?

  • A computer running Linux or macOS
  • Swift 3 installed
  • A MySQL Server

That's basically all that's needed. Now we need a functional database, so I came up with a SQL table like this:

CREATE TABLE `users` (
  `id` int(32) NOT NULL,
  `username` varchar(128) NOT NULL,
  `password` varchar(128) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `users`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `username` (`username`),
  ADD UNIQUE KEY `id` (`id`);

INSERT INTO `users` (`id`, `username`, `password`) VALUES
(1, 'user1', '7c12772809c1c0c3deda6103b10fdfa0'),
(2, 'user2', '7c12772809c1c0c3deda6103b10fdfa0');

ALTER TABLE `users`
  MODIFY `id` int(32) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;

Perfect. Now - for the next part - initialize a new Swift package in an empty folder:

swift package init --type executable

This will generate an empty package, so now it's time to edit our Package.swift file. You'll need to add 2 repositories to your project, this is how I have done that:

import PackageDescription

let package = Package(
    name: "backend-api",
    dependencies: [
        .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2),
        .Package(url: "https://github.com/PerfectlySoft/Perfect-MySQL.git", majorVersion: 2)
    ]
)

Cool. So now it is about time to work on the real deal - programming! I've split the project into two separate swift files

  • main.swift : this is for all things related to the web server processing
  • DBOperations.swift : this is a provider to execute MySQL (prepared) statements in the form of a function

My main.swift:

import PerfectLib
import PerfectHTTP
import PerfectHTTPServer
import Foundation

let server = HTTPServer()
var routes = Routes()

server.serverPort = 8000

routes.add(method: .get, uri: "/", handler: {
	request, response in

	response.status = HTTPResponseStatus.ok
	response.setHeader(.contentType, value: "application/json")

	response.setBody(string: "{\"status\":\"Backend server is up and running\"}")
	response.completed()
})

routes.add(method: .get, uri: "/oauth", handler: {
	request, response in

	response.setHeader(.contentType, value: "application/json")

	let username = request.param(name: "username", defaultValue: "")
	let password = request.param(name: "password", defaultValue: "")

	if ((username ?? "").isEmpty || (password ?? "").isEmpty) {
		response.status = HTTPResponseStatus.ok
		response.setBody(string: "{\"status\":\"Invalid request\"}")
	} else {
		if (username ?? "").characters.count >= 8 || (username ?? "").characters.count <= 24{
			if (password ?? "").characters.count == 32 {
				if (username ?? "").rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil && (password ?? "").rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil {
					response.status = HTTPResponseStatus.ok
					let queryResult: [[[String: String]]] = executeQuery(query: "SELECT * FROM users WHERE username = ?", parameters: [username ?? ""])
					if (queryResult.count == 1) {
						if  ((queryResult[0][2]["password"] ?? "") as String == password ?? "") {
							response.setBody(string: "{\"status\":\"Authentication request succeeded!\"}")
						} else {
							response.setBody(string: "{\"status\":\"Invalid request\"}")
						}
					} else {
						response.setBody(string: "{\"status\":\"Invalid request\"}")
					}
				} else {
					response.status = HTTPResponseStatus.ok
					response.setBody(string: "{\"status\":\"Invalid request\"}")
				}
			} else {
				response.status = HTTPResponseStatus.ok
				response.setBody(string: "{\"status\":\"Invalid request\"}")
			}
		} else {
			response.status = HTTPResponseStatus.ok
			response.setBody(string: "{\"status\":\"Invalid request\"}")
		}
	}

	response.completed()
})

server.addRoutes(routes)

do {
	try server.start()
} catch PerfectError.networkError(let err, let msg) {
	print("Network error thrown: \(err) \(msg)")
}

My DBOperations.swift:

import MySQL

let sqlHost     = "127.0.0.1"
let sqlUser     = "app_authentication"
let sqlPassword = "testing01"
let sqlDB       = "app_authentication"

func executeQuery(query: String, parameters: [String]? = nil) -> [[[String: String]]] {
	var fStore = [[[String: String]]]()

	let mysql = MySQL()
	let connection = mysql.connect(host: sqlHost, user: sqlUser, password: sqlPassword, db: sqlDB)

	guard connection else {
		print(mysql.errorMessage())
		return [[[String: String]]]()
	}

	defer {
		mysql.close()
	}

	let fQuery = MySQLStmt(mysql)

	_ = fQuery.prepare(statement: query)

	if parameters != nil {
		for parameter in parameters! {
			fQuery.bindParam(parameter)
		}
	}

	_ = fQuery.execute()

	let fResults = fQuery.results()

	_ = fResults.forEachRow {
		e in

		var fSubStore = [[String: String]]()

		for i in 0..<e.count {
			let fFieldName: String? = fQuery.fieldInfo(index: i)!.name
			fSubStore.append(["\(fFieldName!)": "\(e[i]!)"])
		}

		fStore.append(fSubStore)
	}

	return fStore
}

You need to save both of these files inside the Sources folder of your package.

To build and run your app, run this in the root of your package directory:

swift build
.build/debug/backend-api

Now it is time to test if everything works:
It works!

So: this is how you can build authentication APIs with Swift. Please bare in mind that all code examples given are NOT production ready. I know that it works, but I can definitely already see some points of improvements - too bad that I don't have time to fix that. This demo project is soon to be expected on my GitHub. Thanks for reading and have a nice day ;)

Bart Simons
Author

Bart Simons

Comments