Credentials-free authentication (part 2)
Showing a simple way to authenticate your users without exposing yourself to data breaches.
Part 2: The iOS app (native, using Swift)
Table of Contents
I. Intro
1.1 The APIs
1.2 New iOS App
II. The Verify Service
2.1 Constants
2.2 VerifyServiceError
2.3 URLSession
2.4 Requesting a Verification
2.5 The Verification Check
2.6 Cancelling a verification
2.7 All together now
III. The Login Screen
3.1 Login Storyboard
3.2 The Router
3.3 Navigation using the Router
3.4 The Login Screen
3.5 Activity Indicator
3.6 Starting the Verification
3.7 Main Screen
I. Intro
In Part 1, we built a Vapor app that we deployed to Heroku. This was needed to secure the Nexmo credentials (and not include them inside the iOS app).
In this second part, we’re going to build an iOS app that will authenticate the user without storing their credentials. It will do this by checking the ownership of a phone number using the APIs we’ve build.
1.1 The APIs
Here is quick summary of the live APIs we built:
- Start verification request:
URL: https://[appname].herokuapp.com/verify/request
Method: POST
Parameters: api_key, number - Fulfil verification:
URL: https://[appname].herokuapp.com/verify/check
Method: POST
Parameters: api_key, request_id, code - Cancel verification:
URL: https://[appname].herokuapp.com/verify/check
Method: POST
Parameters: api_key, request_id
1.2 New iOS App
We are building an app named “Notes” using the Single View App
template:
II. The Verify Service
Let’s start by creating a simple utility class that will be responsible for the interactions with the server — we’ll name it VerifyService
and create a singleton for it to avoid duplicates:
2.1 Constants
To store all the hardcoded values we’ll need, we’ll define the following constants:
- UserDefault — for the default variable names (we’ll use them later)
- Auth — for the length of an authenticated session
- API — host and apiKey values
If you are unsure of why enums are used here, see this blogpost. TLDR: enums with no cases are perfect for namespaced constants.
2.2 VerifyServiceError
We’ll also add, at the top of the VerifyService.swift
file, a VerifyServiceError
enum that we’ll use to express errors:
2.3 URLSession
An URLSession
is also required to ensure that all calls are being sent on a background thread — create a new Swift file named ApiSession.swift
for it:
2.4 Requesting a Verification
Let’s go back to our VerifyService
and continue building it by adding a couple of utility methods. They are going to used to create the URLRequest
s for each of the API calls.
Here is the request call:
2.5 The Verification Check
The verification check follows the same logic: we’re sending a request to /verify/check
with the api_key
, request_id
and code
as parameters:
2.6 Cancelling a verification
To cancel a verification in progress, we’ll need to send a request to /verify/cancel
with the api_key
and request_id
as parameters:
2.7 All together now
To make sure we’re on the same page, a better indented, more legible and complete version the VerifyService.swift file is available at: https://gist.github.com/pardel/9ccf5c418d58257f3e2b30299cc5cc8e
III. The Login Screen
Now that we have the service the deals with the network calls to the APIs, let’s start building the interface of our app. This will consists of mainly 2 parts, one containing the authentication screen and, the other, the rest of the app.
I always find it easier to split these parts into 2 separate storyboards — we’ll use the one we already have (Main.storyboard) for the app and create a new one for the authentication.
3.1 Login Storyboard
We’ll therefore create a new storyboard named Login.storyboard. I also find it easier (and more enjoyable to use) when these app “parts” as arranged in groups:
Let’s start building the storyboard:
- add a UIViewController from the Libray (it is now, in Xcode 10, launched from the toolbar)
- embed the view controller into a navigation controller (from the menu:
Editor
>Embed in…
>Navigation Controller
- set the navigation controller as the entry point for the storyboard (
Is Initial View Controller
checkbox in the Attributes Inspector) - add a second View Controller
- add a “Show” segue from the first view controller to the second so it can be pushed onto the navigation stack — name the segue: “showCode”
- create two Cocoa Touch Class files, subclasses of
UIViewController
, calledLoginViewController.swift
andCodeViewController.swift
- assign
LoginViewController
class to the root (first) view controller andCodeViewController
to the second one
This is how our storyboard looks now:
3.2 The Router
The flow between the authentication part and the main part will be handled by a rudimentary router. Let’s create a new Swift file named Router.swift
and define a Route
as a closure that takes no parameters and returns nothing:
We’ll also define a Router class as a singleton, since we’re going to only need one router:
Our router needs to be able to store routes and navigate to them; so, we’ll add a property routes that stores a dictionary of named routes as well as methods to add and navigate to a route:
Notice the the add and navigateTo methods are static; also,we are using a preconditionFailure
for when a route is not found.
3.3 Navigation using the Router
Let’s now use our Router to in the AppDelegate and add routes for navigating to home
, login
and logout
:
We’ll then call addRoutes
in application:didFinishLaunchingWithOptions
and navigate to either home
or login
depending on the current state:
We’ve used a property on the VerifyService
, hasBeenVerified
, defined as a check for the time when we last verified the user:
Everything should be operational now so let’s Build & Run; you’ll see an empty screen with a navigation bar at the top — our Login view controller. Let’s add some “color” to it!
3.4 The Login Screen
Our login screen should contain just one text field to ask the user for their number. Let’s add just that, as well as a button to start the verification:
We’ll connect the text field to an IBOutlet in LoginViewController and the button to an IBAction:
We’ve also added a title
for the screen and set the phoneTextField
to any value previously entered (and stored in UserDefaults
). If phoneTextField
is empty when the login is display, we’ll also make it the first responder.
3.5 Activity Indicator
As the API calls will be done asynchronously, we’ll need to show the progress to the user whilst preventing her from move to another screen. For this we’ll use the SwiftSpinner library from the prolific Marin Todorov. All you need to do is download the SwiftSpinner.swift file and add it to the Xcode project.
3.6 Starting the Verification
The login action will:
- save number to UserDefaults
- show the SwiftSpinner
- and start a verification process
There is just one more thing we need to do before we move to the next screen — if a verification is in progress, we should automatically advance to the next screen. We can achieve this in viewDidLoad
by checking if the requestId
UserDefault
is set. Here is the complete listing of LoginViewController
:
3.7 Fulfilling the Verification
Once the number is submitted, we should receive an SMS with a 4-digit code that we’ll use to confirm our number.
To submit this, let’s build the interface for the CodeViewController — as before, we’ll need a TextField but no submit button as we’ll send the code once 4 digits are entered. We’ll also add an IBOutlet for it:
We’ll also need to disable the navbar back button and replace it with a cancel button that is displayed 30 seconds later. NB: Nexmo verification requests can’t be cancelled in the first 30 seconds.
The cancellation is straight-forward — a call to VerifyService cancel and going back for either success or error:
Let’s now handle the code submission — we’ll need to add an IBAction
for the “Editing Changed
” event of the codeTextField
(the Connections Inspector is your friend).
That’s it for the CodeViewController — a complete listing can be found at: https://gist.github.com/pardel/c28eecec05239ce5efbc5c3058ae503e
3.7 Main Screen
Once verified, our app is showing a blank white screen. Let’s change that a bit to reflect the authenticated status.
Let’s open the Main.storyboard
and embed the ViewController
into a Navigation Controller just like we did in section 3.1.
Let’s also add, in ViewController.swift
, a title and a logout button:
Build & Run and we’re off to the races!
And here we have it! A complete iOS app that doesn’t require user credentials to authenticate its users!
A complete copy of the project is available at: https://github.com/pardel/NexmoVerify-iOS