How to Add Two-Factor Authentication with Swift and Vapor
Two-factor authentication (2FA) is when you use two different things to verify your identity. Usually, something you know, like a password, paired with a verification code from a physical device like a phone.
This tutorial will cover how to implement a verification token system with the Vonage Verify API and Vapor. Once finished, you can test the system with a SwiftUI application.
- Xcode 12 and Swift 5 or greater.
- Vapor 4.0 installed on your machine.
- ngrok for exposing your local machine to the internet.
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
Create a Vapor Project
You can create a Vapor project using the new project command
vapor new SwiftVerify -n in your terminal. Once the command has finished change directory into the folder, it created for you using
cd SwiftVerify. Now you can open the project in Xcode using
Once Xcode opens, it will start downloading the dependencies that Vapor relies on using Swift Package Manager (SPM). To view the dependencies, you can open the
Create the Model Structs
A major benefit of using Vapor is that you can lean on the Swift language’s type-safety. You can model inputs and outputs to your server using structs that conform to the
Codable protocol; Vapor has a protocol called
Content for this.
Start by creating a struct called
Vonage to house all the model code. Create a new file under Sources > App called
VonageClient.swift. In the new file, create the
The struct is initialized with the API key and secret from your Vonage API account and stores them as local properties for use later. You will be creating two API endpoints in the tutorial, one to request a verification code, and one to check if the code was correct. Create two more structs within the
Vonage struct for this:
These structs have a dual purpose; to use the input into the server and its output. The non-optional properties and properties without a default value, for example,
RequestVerificationBody are supplied when you make a request to the server.
The custom initializer takes in the version of the struct from the request to the server, enrich it with remaining properties then use it to make a call to the Vonage APIs. The Vonage APIs expect fields in snake case, so the structs have the
CodingKeys enum to map their property names to their snake case equivalent.
Create a Verification Request
You need to make a call to the Verify API to create a verification request. The endpoint you want to call to create a verification request is
/verify. Create a function in the
Vonage struct to do so:
The function takes the body of the request made to your server and a
Client. Vapor's Client API allows you to make external HTTP calls. Before the post request gets sent, the body becomes encoded with an enriched
RequestVerificationBody struct. The function returns an
EventLoopFuture which is a generic type that references a value that is not available yet, in your case, the response from the post request.
The next step is to define the route, which is the endpoint on your server that will call the above function. Open
routes.swift, create an instance of the Vonage struct and define the new route:
API_SECRET with your credentials from the Vonage API dashboard. In a production environment, you can use Vapor's Environment API to avoid exposing your credentials.
/request endpoint on your server receives a request, it will decode the body of that request into a
RequestVerificationBody struct, then use it to call the function you created earlier. By default workflow 1 is used, you can add a property to the
RequestVerificationBody, with a coding key mapping to
workflow_id to change this.
The result of the call will have a
status property; when this is 0, it means the action has been successful. It will also include a
request_id; this is what is used to check the code is valid.
Check the Code
Checking if the code is valid is a very similar process. Add a function to the
Vonage struct to call the Verify API, this time making a post request to
/verify/check with a
Then add the route in
Similarly to the earlier request route, the result of the call will have a
status property with 0 meaning success.
Test Your Server
Now that your routes are defined, you can build and run (CMD + R) your server. Once complete, your server will be running locally on port 8080.
To expose this to the internet, you can use ngrok. In your terminal run
ngrok http 8080. A public URL is generated which forwards calls to your local machine.
Now that your server is available on the internet, you can make calls to it, to test your server, you can use the test application. Either download the project or clone with your terminal using
git clone firstname.lastname@example.org:nexmo-community/swiftui-two-factor-app.git.
Once downloaded, open the project in Xcode. In the
VerifyModel.swift file, replace the
BASE_URL string with the forwarding URL from ngrok, then build and run (CMD + R). You can enter your phone number, and it will receive a text which you can enter to verify your phone number!
You can find the completed project on GitHub. There is more you can do with the Verify API such as changing the workflow event timings or using it to authorize payments. Learn more on developer.nexmo.com.