Credentials-free authentication (part 1)
Showing a simple way to authenticate your users without exposing yourself to data breaches.
Part 1: The web server (using Swift & Vapor)
Table of Contents
I. Intro
1.1 “Social Authentication”
1.2 The Better Alternative
1.3 Nexmo Verify
1.4 Vapor
II. Vapor Server
2.1 New Vapor app
2.2 Routes
2.3 Consuming APIs using Postman
III. Building our APIs
3.1 verify/request
3.2 Reading input parameters
3.3 The Output
IV. Nexmo service
4.1 Nexmo Credentials
4.2 Xcode Environment Variables
4.3 Start the Verification
4.4 The HTTP Request
4.5 Nexmo Session
4.6 (cont) Verify Request
4.7 Check Verification
4.8 Cancel Verification
4.9 Summary
V. Vapor Routes
5.1 verify/request
5.2 verify/check
5.3 verify/cancel
5.4 Testing using Postman
VI. Deployment to Heroku
6.1 The Heroku CLI
6.2 New app
6.3 Heroku config vars
6.4 Procfile
6.5 Deploy
NB: In Part 2, we’re going to build an iOS app that consumes the APIs we’re constructing in this article.
I. Intro
Another day, another data breach. Nobody seems to understand that loosing users’ credentials has a bad impact on user trust and revenue — maybe they should ask “the business formally knows as” Yahoo about that.
And what’s even more disturbing is that businesses are suffering because of others’ inability to secure shared data.
1.1 “Social authentication”
Developers are delegating authentication to 3rd parties (Google, Facebook, Twitter, Github, etc.) is order to avoid the headaches of dealing with sensitive data. Like all bad ideas, it sounded good at the time.
I’ve been saying this for a long time now: one should never lock their house with someone else’s key unless it’s your mum. The trust must be complete or the whole exercise becomes futile.
1.2 The Better Alternative
What if I told you there is a very simple way to authenticate your users without storing credentials or delegating to 3rd parties?!
You get to rely on an omnipresent resource: virtually everyone has a phone number; and instead of asking for a password that you check against the version you hold, you send them a short-lived SMS containing a code they need to send back to you.
Here is a list of technologies we’re going to use:
- Nexmo’s Verify APIs — will allow us to send SMS’
- Vapor — the most used web framework for Swift — we’ll build a simple web server (we’ll discuss below the need for a web server)
- iOS — we’re going to build an app that consumes this authentication
The only downside (if you can call it that) is that it costs money, but balancing the need for authentication with good security can bring those costs down.
Plus, you are building a profitable business where you charge users for services, right?! 🤨
1.3 Nexmo Verify
Nexmo is a business unit of Vonage that develops and markets communication APIs. One of their first available APIs is Verify which allows you to send a PIN (by SMS and phone) to prove a user can be reached at a specific number.
We’re going to be using the Verify APIs directly so we’ll need to authenticate via an API key and Secret. As we don’t want to include the key and secret into our iOS app, we’ll create a simple web app that will act as the intermediary — a great opportunity to use Vapor and one language for everything :).
A good description of the flow we’ll need to follow can be found here: https://developer.nexmo.com/verify/guides/verify-a-user:
- Send a phone verification code and receive a
request_id
- User receives code on their phone via a text message or text-to-speech
- User enters verification code.
- We check the phone verification code using code and
request_id
1.4 Vapor
Vapor is the most used web framework for Swift — it’s not really production ready yet (as of v3) but lots of developers are using it for small projects.
Before we start, make sure you have a relatively recent version of Swift installed on your Mac — mine is 4.2:
$ swift --version
Apple Swift version 4.2 (swiftlang-1000.11.37.1 clang-1000.11.45.1)
Target: x86_64-apple-darwin18.0.0
We’re going to install Vapor via Homebrew:
$ brew install vapor/tap/vapor
…
🍺 /usr/local/Cellar/vapor/3.1.10: 4 files, 15.6MB, built in 2 seconds$ vapor --version
Vapor Toolbox: 3.1.10
II. Vapor Server
2.1 New Vapor app
We’re now ready to create our web app:
$ vapor new Verify --template=api
Go into the project folder and generate an Xcode project — this will take a bit of time as all dependencies are being pulled in (-y option is open Xcode automatically):
$ cd Verify
$ vapor xcode -y
Generating Xcode Project [Done]
Select the `Run` scheme to run.
Open Xcode project?
y/n> yes
Opening Xcode project…
We now have a Xcode project ready to use:
Make sure you’ve selected the “Run” scheme and “MyMac” as the Target:
… and press ⌘R to run the server. You should now see a “Server starting on http://localhost:8080” message in the Console.
Navigating to that URL, you’ll get to see the server in action:
2.2 The Routes
Routing is the process of finding the appropriate response to an incoming request and, in Vapor, we define our routes in the Sources > App > routes.swift file.
By default, Vapor includes some a sample Controller and Model together with their routes — let’s get rid of them and only leave the root route:
We’ll now add our 3 routes:
- verify/request — we’ll use this to kickstart a number verification process
- verify/check — we’ll use this to send back the code received
- verify/cancel — allows us to cancel an ongoing request
As we’re going to be sending data, we’ll define them as POST requests:
Build and Run your Xcode project again to make those routes available. Now, since we’re using POST we won’t be able to use the browser anymore, so we’ll use a free tool called Postman.
2.3 Consuming APIs using Postman
Postman is an extensive collection of tools that makes it easy to consume APIs. We’re going to be using their Mac app available here.
We’ll start by creating a Request — I’ve named mine Request and stored in under a Verify collection. Use the following parameters:
- method: POST
- url: http://localhost:8080/verify/request
Once you hit Send, a “request verification” response should be received.
III. Building our APIs
3.1 verify/request
It will come to no surprise that the first API we’re going to build is the one were we initiate a verification. The parameter our API would need to accept is the number to verify.
In addition to that, we’ll add another parameter, called api_key. This is a very simple security measure in case someone inadvertently discovers our APIs — we don’t want people to spam or empty our Nexmo balance 🤓. It could also be used to remove access if required later on.
So the parameters we’ll send are:
- number — string containing the phone number in international format. eg: 447xxxxxxxxx. Goes without say that you should use your own number here to receive verification codes.
- api_key — string containing an unique identifier — I’m gonna to use ‘b6AK9o8kVx3n’ but feel free to pick any random value.
Let’s update our Postman request to include them in the body of the HTTP request:
3.2 Reading input parameters
Handling the input parameters in Vapor is pretty easy: we’ll need to create a Codable struct that represents our input. We’ll call it VerifyRequestParameters and we’re going to use it to decode the request’s content:
Build&Run and send that request again. You’ll notice that the params inside the route is a Future:
request verification with NIO.EventLoopFuture<App.(unknown context at 0x10050e510).VerifyRequestParameters>
If you are not familiar with the concept of Futures and Promises, the prolific John Sundell wrote a great article explaining the concepts behind them.
Vapor contains an Async library that makes using Futures relatively easy.
3.3 The Output
We’ll send back a JSON response that includes:
- request_id — string containing the request_id from Nexmo, if the call was successful.
- error — string containing an error description, if call was unsuccessful.
NB: both request_id and error are optional as they depend on the call’s
Vapor makes sending JSON output easy — all we have to do is to send back a Codable struct. We’ll name it VerifyRequestResponse (I know the naming can be confusing here).
We’ll also have to send back a future if we want to consume the input parameters. In the listing below, I’m sending back the number as the request_id — this is a stopgap solution until we build the service that talks to the Nexmo APIs.
Build&Run and check the response in Postman.
IV. Nexmo Service
Let’s add a simple class that handles all the communication with Verify - we’ll name it NexmoService and include couple of properties we’ll need.
4.1 Nexmo Credentials
Let’s create a file inside the App group and name it NexmoService.swift:
The api_key
, api_secret
and myapp_api_key
will be set as environment variables (we’ll see how to do this later in Heroku). I’ve decided to add them as static non-optional to make our life easier.
NEXMO_API_KEY and NEXMO_API_SECRET are the Nexmo credentials unique to your app. You can retrieve yours from your Nexmo dashboard:
4.2 Xcode Environment Variables
For our local development, we’re going to inject those credentials inside the Xcode scheme as Environment Variables for the Run stage.
Now, you might think there is a security issue here but our project’s .gitignore
contains the project so it won’t be committed to version control. You’re using version control, right?!
For MYAPP_API_KEY
, add a random string — this is unique to our Vapor app and we’ll be using it later when consuming the APIs.
The only downside is:
You’ll need to re-add the credentials if you regenerate the project.
4.3 Start the Verification
The first Nexmo API we’re going to consume is the verify request: https://developer.nexmo.com/api/verify#request
For this, inside the NexmoVerify class, we’ll add a method that takes a phone number as the input and returns either a request_id
or an error
. As the API call will happen asynchronously, we’re gonna employ escaping closures.
We can define NexmoServiceError
is an enum that conforms to the Error
protocol. I went ahead and added all error cases we’re going to deal with:
Now, let’s go back to our verify method — we’re going to start by creating an URLRequest
.
4.4 The HTTP request
We’ll write a simple private method called httpRequest that takes in a path and a list of parameters and returns an URLRequest?
.
We’ve also used it inside the startVerification
method to create a URLRequest
to “verify/json” endpoing.
4.5 NexmoSession
Now that we have the request, let’s use it to run a dataTask. In order to ensure that our requests are always sent on the background thread, we’ll create a custom URLSession.
Add a new Swift file to the project and name it NexmoSession.swift:
The code above is pretty straightforward — we use a standard URLSessionConfiguration
and a background OperationQueue
to construct a shared URLSession
to be used for all calls.
4.6 (cont) Start Verification
We’re now ready to send the request to Verify:
Let’s go through the code line by line:
- lines 11–15: create the
URLRequest
for ‘verify/json’ with the number and brand as parameters - lines 16–17: create a data task on the shared
NexmoSession
- lines 18–20: handling any errors we encounter on performing the data task
- line 22: create the JSON decoder for the returned data
- lines 23–27: create a decodable entity that defines the response received
- lines 28–32: check for returned data in a valid JSON format
- lines 33–43: handle various scenarios — we only return success if the status response is either 0 or 10 and a request_id is present
4.7 Check Verification
Following the same flow as above, and reusing the httpRequest method, here is how the verification check looks like:
This time, we’re sending a request to ‘verify/check/json’ with the request_id (received on the previous call) and the code received via SMS.
The response returned includes the status of the check, our request_id as well as pricing information.
4.8 Cancel Verification
The last call to send is the cancellation — the parameters required are the request_id and a cancel cmd:
4.9 Summary
We’re now ready to “consume” our NexmoService
inside the Vapor routes.
To make it easier to understand and use, here is our NexmoService “signature”:
V. Vapor Routes
Back in section 3.3, in routes.swift
, we’ve defined this promise that we’re now ready to fulfil :)
5.1 verify/request
The verify/request
route is going to start the verification process by using the startVerification
method of our NexmoService
.
In lines 7-10 , we’re checking our service api_key against the provided parameters.
Word of warning — make sure you build the next 2 routes before trying to use this one. The Nexmo service will call your number to relay the code by voice if you don’t confirm it within the first couple of minutes. This can become a nuisance if you’re still building the check or cancel calls.
5.2 verify/check
The verify/check
route is going to fulfil the verification process by using the checkVerification
method:
5.3 verify/cancel
Finally, verify/cancel will send the cancellation:
That’s it! We now have a fully operational Vapor server that uses the Nexmo’s Verify to provide an API to authenticate users.
Build&Run, and we’re ready to test it!
For reference, a complete copy of the project is available at: https://github.com/pardel/NexmoVerify-Vapor
5.4 Testing using Postman
Let’s test the service by using Postman as shown in section 2.3.
In addition to the call we’re already built, let’s add 2 more. One for the verify/check
:
and one for verify/cancel
:
Make sure the right api_key value is added as defined in the Xcode schema run parameters (section 4.2).
We’re now ready to try it. Make sure you have your phone handy and ready to receive text messages — this is how they look like:
Here are couple of scenarios we should try (make sure you copy the request_id
returned in the first step, into the subsequent calls):
- Success scenario: Request, Check
- Success scenario: Request, Cancel (NB: there is a 30 seconds delay before a request can be cancelled).
- Success scenario: Request, Request, Check (2nd call will not return an error but the same
request_id
as 1st call) - Error scenario: Request, Check, Cancel (3rd call will return error — verification already checked)
- Error scenario: Request, Cancel, Cancel(3rd call will return error — verification already cancelled)
VI. Deployment to Heroku
We’re now ready to deploy to a live server. Before you proceed any further, make sure you’ve committed all your changes:
$ git add .
…
$ git commit -m ‘Verify server operational’
6.1 The Heroku CLI
The simplest way for us to deploy is using the Heroku CLI which makes it easy to create and manage your Heroku apps directly from the terminal.
The CLI can be install using a MacOS Installer or via brew — instructions are available at: https://devcenter.heroku.com/articles/heroku-cli
Once that’s completed, we have access to a heroku
command:
$ heroku -- version
heroku/7.18.2 darwin-x64 node-v10.12.0
We’ll also need to login into our account:
$ heroku login
Enter your Heroku credentials.
Email: paul@example.com
Password (typing will be hidden):
Authentication successful.
Vapor includes a command that, in theory, would allow anyone to deploy to Heroku easily:
vapor heroku init
This creates a Heroku app using their latest stack, Heroku-18
, which is based on Ubuntu 18.04. Unfortunately, “Vapor does not support OpenSSL 1.1 — the new default on 18.04 — on the source level, so it is not compatible with Ubuntu 18.04 in the end”. This issues has already been fixed but not released yet.
Until that release happens, we’ll need to deploy using the Heroku CLI.
6.2 New app
Heroku servers support lots of programming language but unfortunately, Swift is not on the official list yet. Fret not — Heroku allows us to add our own scripts that are run when your app is deployed, called Buildpacks. They are used to install dependencies for your app and configure your environment.
We’ll use the heroku-16
stack and the Vapor buildpack to create our app:
$ heroku create --stack=heroku-16 --buildpack=https://github.com/vapor-community/heroku-buildpack
Creating app… done, ⬢ stark-mesa-11992, stack is heroku-16
Setting buildpack to https://github.com/vapor-community/heroku-buildpack... done
https://stark-mesa-11992.herokuapp.com/ | https://git.heroku.com/stark-mesa-11992.git
We now have a Heroku app called stark-mesa-11992
(you will get a different name) which can be seen in your Heroku control panel:
Let’s add the newly created app to our repository — this will allow us to deploy directly to the Heroku app:
$ heroku git:remote -a stark-mesa-11992
set git remote heroku to https://git.heroku.com/stark-mesa-11992.git
There are 2 more things we need to do before we can deploy to it — setting the Heroku config vars and adding a Procfile.
6.3 Heroku config vars
Seting the config vars can be done via the command line using the CLI (make sure you use your Nexmo credentials):
$ heroku config:set MYAPP_API_KEY=b6AK9o8kVx3n
Setting MYAPP_API_KEY and restarting ⬢ stark-mesa-11992… done, v3
MYAPP_API_KEY: b6AK9o8kVx3n
$ heroku config:set NEXMO_API_KEY=xxx
Setting NEXMO_API_KEY and restarting ⬢ stark-mesa-11992… done, v4
NEXMO_API_KEY: xxx
$ heroku config:set NEXMO_API_SECRET=xxx
Setting NEXMO_API_SECRET and restarting ⬢ stark-mesa-11992… done, v5
NEXMO_API_SECRET: xxx
These config var values could also be set or changed using the app’s Settings panel:
6.4 Procfile
The last thing we need to do before we can deploy is adding a Procfile
to the project so Heroku will know how to run the app:
$ echo ‘web: Run serve --env production --hostname 0.0.0.0 --port $PORT’ > Procfile
$ git add Procfile
$ git commit -m ‘adds Procfile’
[master 71bba1e] adds Procfile
1 file changed, 1 insertion(+)
create mode 100644 Procfile
And with that, we’re ready!
6.5 Deploy
It’s all gonna come together now :). Make sure you use your own Heroku app name below:
$ git push heroku master
Counting objects: 56, done.
…
remote: — — -> Installing dynamic libraries
remote: — — -> Installing binaries
remote: — — -> Cleaning up after build
remote: — — -> Discovering process types
remote: Procfile declares types -> web
remote:
remote: — — -> Compressing…
remote: Done: 8.5M
remote: — — -> Launching…
remote: Released v6
remote: https://stark-mesa-11992.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy… done.
To https://git.heroku.com/stark-mesa-11992.git
* [new branch] master -> master
The app is now deployed — let’s add a web dyno to run the app:
$ heroku ps:scale web=1
Scaling dynos… done, now running web at 1:Free
And we can now open the app either by typing the URL in a browser or using:
$ heroku open
Congratulations! You’ve now deployed a Vapor app to Heroku!
We can now use Postman to test the calls — all we have to do is replace http://localhost:8080 with https://stark-mesa-11992.herokuapp.com (make sure you use your own app name).
That’s it for this tutorial! In the next one we’re going to build an iOS app that will use this Heroku app to authenticate users without requiring a password.