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 vapor xcode.

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 Package.swift file.

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 Vonage struct:

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, number on 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:

Replace API_KEY and 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.

When the /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 CheckVerificationBody struct:

Then add the route in routes.swift:

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

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



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store