Using MongoKitten 3 + Vapor for your applications

This article is for developers that are familiar with Swift 3 to develop their first Web Applications using MongoKitten and Vapor. In this tutorial we're going to make a simple login page that you can authenticate users on.

This tutorial assumes you’re already familiar with Swift 3 and are running the release of Swift 3.0.1.

Besides this this tutorial assumes you have a working MongoDB server of version 2.6 or greater running on localhost. Some features in MongoKitten 3 may not be available for you if you’re running an older version of MongoDB. Specifically aggregate pipeline stages may not be available if you don’t run MongoDB 3.4.

What is MongoKitten?

MongoKitten is a MongoDB connector that’s written purely in and for Swift 3 and is aimed primarily at Linux servers. Almost all are dependencies maintained by the same developers. MongoKitten is aimed at high performance whilst maintaining a simple and powerful API.

MongoKitten was written by me with help from Robbert Brandsma. We collaborate and interact with our community members and usually respond within a day on every issue.

The reason why we created MongoKitten because all other libraries didn’t meet our standards. We originally came from the Dart community initially before working on Server-Side Swift. Dart has a MongoDB library written purely in Dart which offers a great experience. Other MongoDB libraries in Swift have (or had) too little attention to their API and too little support for more advanced MongoDB features/standards like GridFS and database administration. Besides that they all still depend on the installation of a C library using either apt or homebrew.

Setting up your dependencies and boilerplate

First we’ll make a new project in our Documents folder.

mkdir -p ~/Documents/Tutorial/
cd ~/Documents/Tutorial/
swift build init --type=executable

Next we'll edit the Package.swift to contain MongoKitten and Vapor.

import PackageDescription
let package = Package(
name: “Tutorial”,
dependencies: [
.Package(url: “https://github.com/OpenKitten/MongoKitten.git", majorVersion: 3),
.Package(url: “https://github.com/vapor/vapor.git", majorVersion: 1)
]
)

And assuming you use Xcode we’ll create and open an Xcode project.

swift package generate-xcodeproj
open Tutorial.xcodeproj

So now it's time to write some code.. We'll start by importing our dependencies and setting up the MongoKitten and Vapor boilerplate.

Within a do-catch we'll try to connect with the MongoDB Server.
And in there we're subscripting the mongoDB server with a string to access the database called "tutorial".. which we're subscripting again to get access to the collection instance. A collection is another name for a table for those familiar with SQL.

And finally we'll start the application on port 8080.

import MongoKitten
import Vapor
let drop = Droplet()
do {
let mongoDB = try Database(mongoURL: "mongodb://localhost/mydatabase")
let users = mongoDB["users"]
 // Add your application here

drop.run()
} catch {
print("Cannot connect to MongoDB")
}

In the mongoURL parameter you can put the entire MongoDB connection URL including your username, password, port and SSL options. SSL is not supported yet but will be implemented using the default MongoDB connection URI parameter.

let mongo = try Database(mongoURL: "mongodb://username:password@example.com:1234/mydatabase?ssl")

In this case we're connecting with the user "username" and the password "password". The server is located at "example.com" and the port is 1234. SSL is enabled.

Creating a simple login page

So the next step is to actually make a simple page. I've used the following code in the place of the comment. I’m doing so by building a one-page application. Our login system will user the path “http://localhost:8080/” as the page for all functionality. And because we don’t write any HTML for this tutorial I’m using UNSAFE query parameters for authentication. This is a really bad practice and is only done for the sake of this tutorial’s simplicity.

drop.get("") { request in
...
}

This is the code that’ll start replacing the comment. Next in this closure we’ll add some code for managing sessions instead of the three dots.

guard let session = request.session else {
return "We require you to use cookies for this login system"
}

This checks whether Vapor was able to set up a session. If this isn’t successful we can’t store login information.

Next we’ll check if the user is logged in and give them a message with their username if they are. We do this by taking a variable from the session. And we try to find someone in the database with the ID that will be stored in the session. MongoDB uses ObjectID’s which are a 12-byte unique identifier. This is usually represented as a 24-character hexadecimal string. So to find a user with the stored string we’ll need to instantiate a BSON ObjectID from the string.

This is the full code of the login-check:

if let userID = session.data[“user”] {
guard let userDocument = try users.findOne(matching: “_id” == ObjectId(userID)) else {
return “I don’t know you..”
}

return “Welcome, \(userDocument["username"] as String? ?? "").”
}

Next we’ll add the functionality for logging in and registering users.
In either case we need the user to submit their username and password.

if let username = request.data[“username”] as String?, let password = request.data[“password”] as String? {
...
}

First we’ll hash the user’s password. When we’re comparing the password with the one stored in the database we’ll need to use this hash. And if we’re going to register this user we’ll also need to store the hash.. not the plaintext password. This is a very important part of securing your user’s information.

For this Vapor offert a hashing function (SHA2) for hashing passwords.

let passwordHash = drop.hash(password)

Normally I’d recommend using a Key Derivation Function like PBKDF2 for hashing their passwords. But since we’re not covering security I won’t cover that here.

Next we’ll check if the user is planning on registering:

if request.data[“register”] as Bool? == true {
...
}

Inside this statement we’ll check if this username is already being used. We’re doing this case-sensitive. So “Bob” and “bob” are two separate accounts.

guard try users.count(matching: “username” == username) == 0 else {
return “User with that username already exists”
}

And if this guard statement passes we’ll create and log in the user.
We first insert the user in the users-collection. For this we’re making a BSON document which is similar to a dictionary. The primary difference is that variables need the `~` prefix-operator.

So a BSON Document (Dictionary) looks like this:

[“username”: username, “password”: passwordHash] as Document

The insert method returns the inserted identifier(s). So if you didn’t generate your own ObjectID or other identifier for MongoDB, MongoKitten will generate an ObjectId one for you. This ObjectID can be retreived from insert’s output.

Since (almost) all identifiers are convertible to a string it’s easy to convert it to a string using an extraction getter variable like .string , however we’ll also need to convert the identifier to Vapor’s Node to be able to put it inside the session.

guard let id = try users.insert(["username": username, "password": passwordHash] as Document).string else {
return "Unable to automatically log in"
}

And last but not least we’ll let Vapor respond to the client:

return “Thank you for registering \(username). You are automatically logged in”

The full code after the guard statement looks like this:

guard let id = try users.insert(["username": username, "password": passwordHash] as Document).string else {
return "Unable to automatically log in"
}
session.data[“user”] = Node.string(userId)

return “Thank you for registering \(username). You are automatically logged in”

And finally we’ll need to login an already registered user.
After the if-statement that’s made for registering users we’ll type the code for logging in this user.

// try to log in the user
guard let user = try users.findOne(matching: “username” == username && “password” == passwordHash), let userId = user["_id"] as String? else {
return “The username or password is incorrect.”
}
session.data[“user”] = Node.string(userId)
return “Your login as \(username) was successful”

We’re going to find this user by it’s username and hashed password.
And we’re asking BSON for a non-raw (interpreted) String value for the _id key. In the case of the ObjectId it’s taking the hexadecimal String

In the end the code looks like this:

import MongoKitten
import Vapor
let drop = Droplet()
do {
// Connect to the MongoDB server
let mongoDatabase = try Database(mongoURL: "mongodb://localhost/mydatabase")
// Select a database and collection
let users = mongoDatabase["users"]
drop.get("/") { request in
// Check if there is a Vapor session.. Should always be the case
let session = try request.session()
// Find the user's ID if there is any
if let userID = session.data["user"]?.string {
// Check if the user is someone we know
guard let userDocument = try users.findOne(matching: "_id" == ObjectId(userID)) else {
// If we don't know the user (should never occur)
return "I don't know you.."
}
    // If we know the user
return "Welcome, \(userDocument["username"] as String? ?? "")."
}
  // If the user has sent his username and password over POST, PUT, GET or DELETE
if let username = request.data["username"] as String? , let password = request.data["password"] as Sting? {
let passwordHash = try drop.hash.make(password)

// When the user wants to register
if request.data["register"]?.bool == true {
// If the username already exists
guard try users.count(matching: "username" == username) == 0 else {
return "User with that username already exists"
}
      // Register the user by inserting his information in the database
guard let id = try users.insert(["username": username, "password": passwordHash] as Document).string else {
return "Unable to automatically log in"
}
      session.data["user"] = Node.string(id.string ?? "")
return "Thank you for registering \(username). You are automatically logged in"
}
    // try to log in the user
guard let user = try users.findOne(matching: "username" == username && "password" == passwordHash), let userId = user["_id"] as String?
else {
return "The username or password is incorrect."
}
    // Create a session for this user
session.data["user"] = Node.string(userId)
return "Your login as \(username) was successful"
}
  // If there is no submitted username or password AND the user isn't logged in
return "Welcome to this homepage!"
}
drop.run()
} catch {
print("Cannot connect to MongoDB")
}

This is how we create a MongoDB login system using swift.
You can test your application using the following URLs:

To register and log in: http://localhost:8080/?username=myusername&password=hunter2&register=true
To log in: http://localhost:8080/?username=henk&password=bob
To visit the home page: http://localhost:8080/