Swift + Perfect + Authentication API Example

 •  Filed under swift, perfect, example, authentication

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 ;)