Build the future of communications.
Start building for free

How to do Phone Verification in iOS Apps with Twilio Verify and Swift

98Q4-3B8NQhrInO27R0NIb3c9VKwPy4C56MQ6yMPPfRyqEvw-eINAPDZCIhHN6TSpqZ-q-OAlN2whmoEXcak1dIyZ02K-QITXLG3fedFkUAgbS5YA8_BdxF2y6ojiRzNA5nXPU00-1

Security is at the top of everyone’s mind and phone verification is a simple way to secure your application and help prevent bot accounts. Phone verification is a common security tool used when people sign up for a product or give you their phone number for the first time.

Confidence in your users’ phone numbers decreases fraud and increases reliability of notifications. Let’s take a look at how to verify phone numbers from an iOS application using Swift and the Twilio Verify API.

This tutorial will walk you through the process of SMS phone verification step by step. You can check out the final project on my GitHub.

What you’ll need

To code along with this post, you’ll need:

Setting up

Navigate to the Twilio Console and grab your Verify App “Production API Key” (found under Settings).

https://www.twilio.com/blog/wp-content/uploads/2018/07/verify-project-create.mp4

Open up Xcode and create a new Project. Choose “Single View App”, give your product a name like Phone Verification, and hit Create.
Next, create a new file (shortcut: ⌘N), choose the Property List template, and name the file Keys.plist. This will store your API key.
Add your API key under the Root with the key of apiKey and value of your Verify API key.

If you’re tracking your project using Git, make sure to add Keys.plist to your .gitignore file. This is all the setup we should need to get our app started.

Phone Verification made easy with Twilio Verify

In order to do phone verification, our app will follow this workflow:

  • The user enters two pieces of information to our application:
    • Phone number
    • Country code
  • Our application makes a request to the Verify API to initiate the verification
  • The user receives a text message with a 4 digit code
  • The user then enters that code into a form in the application
  • The application sends the code off to the Verify API, with the user’s phone number and country code, to check the verification
  • If it is correct then the user has verified their number
  • With only two API calls and three views, you’ll be able to verify your users’ phone numbers.

Phone Verification in Swift

Let’s test out our setup in Swift. We’ll be using URLSession to make our HTTP requests, but my coworker Sam has a full overview of the other ways to make HTTP requests in Swift 3.

Add the following code to your ViewController.swift file:

import UIKit

class ViewController: UIViewController {

    private static let baseURLString = "https://api.authy.com/protected/json"
    
    static let path = Bundle.main.path(forResource: "Keys", ofType: "plist")
    static let keys = NSDictionary(contentsOfFile: path!)
    static let apiKey = keys!["apiKey"] as! String
    
    @IBOutlet var countryCodeField: UITextField! = UITextField()
    @IBOutlet var phoneNumberField: UITextField! = UITextField()
    @IBAction func sendVerification() {
        if let phoneNumber = phoneNumberField.text,
            let countryCode = countryCodeField.text {
            ViewController.sendVerificationCode(countryCode, phoneNumber)
        }
    }
    
    static func sendVerificationCode(_ countryCode: String, _ phoneNumber: String) {
        
        let parameters = [
            "api_key": apiKey,
            "via": "sms",
            "country_code": countryCode,
            "phone_number": phoneNumber
        ]
        
        let path = "phones/verification/start"
        let method = "POST"
        
        let urlPath = "\(baseURLString)/\(path)"
        var components = URLComponents(string: urlPath)!
        
        var queryItems = [URLQueryItem]()
        
        for (key, value) in parameters {
            let item = URLQueryItem(name: key, value: value)
            queryItems.append(item)
        }
        
        components.queryItems = queryItems
        
        let url = components.url!
        
        var request = URLRequest(url: url)
        request.addValue(apiKey, forHTTPHeaderField: "X-Authy-API-Key")
        request.httpMethod = method
        
        let session: URLSession = {
            let config = URLSessionConfiguration.default
            return URLSession(configuration: config)
        }()
        
        let task = session.dataTask(with: request) {
            (data, response, error) in
            if let data = data {
                do {
                    let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
                    
                    print(jsonSerialized!)
                }  catch let error as NSError {
                    print(error.localizedDescription)
                }
            } else if let error = error {
                print(error.localizedDescription)
            }
        }
        task.resume()
    }
}

Navigate to Main.storyboard and drag two Text Fields and one Button from the Object library onto the canvas. In the Attributes Inspector add placeholder text for the two Text Fields: one should read “Country Code” and the other should read “Phone Number”. Change the Button text to “Verify Phone Number”.


Control-click on the View Controller icon and drag to the Country Code text box then select countryCodeField as the outlet. Repeat that for the Phone Number text box by selecting phoneNumberField.

Next control-click on the Verify Phone Number button, drag to the View Controller icon, and select the sendVerification event.

Back in ViewController.swift you’ll know that the fields are connected if the IBOutlets and IBAction have the circles filled in on the left like this:

Build and Run the project with Command-R (⌘R).

Type in your country code and phone number and click Verify Phone Number. You should get an SMS message with a 4 digit code:

Hooray! We’re making progress. We haven’t configured the app to display any feedback in the interface yet, so check the Xcode console for any issues if you do not receive an SMS. A successful verification start should log a message like this:

Building a Verification Workflow

We’ll want to separate out our ViewController logic because we will need multiple views:

  1. Start a verification (what we already did)
  2. Check a verification
  3. Display verification results

Create three new Swift files to accompany these views:

  • StartVerificationViewController.swift
  • CheckVerificationViewController.swift
  • VerificationResultViewController.swift

Also create a file to hold the API definition and code for making the API request:

  • VerifyAPI.swift

Open up VerifyAPI.swift and add the following code. This is similar to what we already added in ViewController.swift, but adds the function for checking verification codes and some helper definitions for parsing results and errors.

import Foundation

enum VerifyError: Error {
    case invalidUrl
    case err(String)
}


protocol WithMessage {
    var message: String { get }
}

enum VerifyResult {
    case success(WithMessage)
    case failure(Error)
}


class DataResult: WithMessage {
    let data: Data
    let message: String
    
    init(data: Data) {
        self.data = data
        self.message = String(describing: data)
    }
}

struct CheckResult: Codable, WithMessage {
    let success: Bool
    let message: String
}


struct VerifyAPI {
    private static let baseURLString = "https://api.authy.com/protected/json"
    
    static let path = Bundle.main.path(forResource: "Keys", ofType: "plist")
    static let keys = NSDictionary(contentsOfFile: path!)
    static let apiKey = keys!["apiKey"] as! String
    
    static func createRequest(_ path: String,
                              _ method: String,
                              _ parameters: [String: String],
                              completionHandler: @escaping (_ result: Data) -> VerifyResult) {
        
        let urlPath = "\(baseURLString)/\(path)"
        var components = URLComponents(string: urlPath)!
        
        var queryItems = [URLQueryItem]()
        
        for (key, value) in parameters {
            let item = URLQueryItem(name: key, value: value)
            queryItems.append(item)
        }
        
        components.queryItems = queryItems
        
        let url = components.url!
        
        var request = URLRequest(url: url)
        request.addValue(apiKey, forHTTPHeaderField: "X-Authy-API-Key")
        request.httpMethod = method
        
        let session: URLSession = {
            let config = URLSessionConfiguration.default
            return URLSession(configuration: config)
        }()
        
        let task = session.dataTask(with: request) {
            (data, response, error) -> Void in
            if let jsonData = data {
                let result = completionHandler(jsonData)
                print(result)
            } else {
                // error, no data returned
            }
        }
        task.resume()
    }
    
    static func sendVerificationCode(_ countryCode: String, _ phoneNumber: String) {
        let parameters = [
            "api_key": apiKey,
            "via": "sms",
            "country_code": countryCode,
            "phone_number": phoneNumber
        ]
        
        createRequest("phones/verification/start", "POST", parameters) {
            json in
            print(json)
            return .success(DataResult(data: json))
        }
    }
    
    static func validateVerificationCode(_ countryCode: String, _ phoneNumber: String, _ code: String, segue: @escaping (CheckResult) -> Void) {
        
        let parameters = [
            "api_key": apiKey,
            "via": "sms",
            "country_code": countryCode,
            "phone_number": phoneNumber,
            "verification_code": code
        ]
        
        createRequest("phones/verification/check", "GET", parameters) {
            jsonData in
            
            let decoder = JSONDecoder()
            do {
                let checked = try decoder.decode(CheckResult.self, from: jsonData)
                DispatchQueue.main.async(execute: {
                    segue(checked)
                })
                return VerifyResult.success(checked)
            } catch {
                return VerifyResult.failure(VerifyError.err("failed to deserialize"))
            }
        }
    }
}

Display Verification Result

Now we can call these functions from our view controllers. Let’s work our way backwards and start by updating the VerificationResultViewController file. This view controller will let us know if we entered the correct verification code by displaying a message that lets us know what happened. Replace the existing code with the following:

import UIKit

class VerificationResultViewController: UIViewController {
    
    @IBOutlet var successIndication: UILabel! = UILabel()
    
    var message: String?
    
    override func viewDidLoad() {
        if let resultToDisplay = message {
            successIndication.text = resultToDisplay
        } else {
            successIndication.text = "Something went wrong!"
        }
        super.viewDidLoad()
    }
}

In Main.storyboard drag a new View Controller onto the canvas and add a Label. I made the default text Success Indication Here but our ViewController code will be updating that programmatically.

In the Identity Inspector, update the Custom Class to be VerificationResultViewController.

Next, control-click the View Controller icon on the Result View Controller and drag to the Label. Choose successIndication as your Outlet.

Check Verifications

Next, we’ll do something similar with our CheckVerificationViewController. This View Controller will validate that the code the user enters is the one we’re expecting. We don’t have to track the code we sent anywhere, the Twilio Verify API does that for us!

In that file add the following code:

import UIKit

class CheckVerificationViewController: UIViewController {
    
    @IBOutlet var codeField: UITextField! = UITextField()
    @IBOutlet var errorLabel: UILabel! = UILabel()
    
    var countryCode: String?
    var phoneNumber: String?
    var resultMessage: String?
    
    @IBAction func validateCode() {
        self.errorLabel.text = nil // reset
        if let code = codeField.text {
            VerifyAPI.validateVerificationCode(self.countryCode!, self.phoneNumber!, code) { checked in
                if (checked.success) {
                    self.resultMessage = checked.message
                    self.performSegue(withIdentifier: "checkResultSegue", sender: nil)
                } else {
                    self.errorLabel.text = checked.message
                }
            }
        }
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "checkResultSegue",
            let dest = segue.destination as? VerificationResultViewController {
            dest.message = resultMessage
        }
    }
}

Back in Main.storyboard add another View Controller between Start View and Result View. We’ll add the transitions between those soon but first drag a Text Field, Label, and Button onto the canvas. Update the Label text to be red—this will be for any errors—and change the button text to be Validate.

Then update the Custom Class to be CheckVerificationViewController. Drag from the View Controller icon to the Text Field and select codeField. Now control-click the Validate button and drag to the View Controller icon and select validateCode under “Sent Events”.

Start Verification

We can use our existing View Controller for the Start Verification. Add the following code to the StartVerificationViewController.swift file. It’s some of the code from the original ViewController file.

import UIKit

class StartVerificationViewController: UIViewController {
    @IBOutlet var phoneNumberField: UITextField! = UITextField()
    @IBOutlet var countryCodeField: UITextField! = UITextField()
    
    @IBAction func sendVerification() {
        if let phoneNumber = phoneNumberField.text,
            let countryCode = countryCodeField.text {
            VerifyAPI.sendVerificationCode(countryCode, phoneNumber)
        }
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let dest = segue.destination as? CheckVerificationViewController {
            dest.countryCode = countryCodeField.text
            dest.phoneNumber = phoneNumberField.text
        }
    }
}

Navigate back to Main.storyboard and update the Custom Class for the existing sending a verification view to be StartVerificationViewController. Make sure the references for outlets are still in place. You can now delete the original ViewController.swift file.

Your canvas should look like this:

Wiring it Together

We need to create the segues between our views. First, control-click the Verify Phone Number button and drag it to the Check Verification View Controller. Select ‘Show’ as the segue type.

The next segue will be a bit different. With the Verification Result View Controller selected, head to the Connections Inspector and select and drag from the Present Modally segue to the Check Verification View Controller and select “manual”.

https://www.twilio.com/blog/wp-content/uploads/2018/07/manual-segue.mp4

Then select the segue arrow and, in the Attributes Inspector, update the identifier to be checkResultSegue.

Test out your validation

Now that all of the views are linked together, build the project again (⌘R) and try out the entire workflow. You should be able to receive a verification code and validate it using your new iOS application! Try entering a bad token to see the error message.

Flow chart of our application Flow chart of our application

Next Steps

There are a few more ways you can customize your verification flow. The default verification code we used is 4 digits, but you can support custom code lengths up to 10. You can also customize the message based on the user’s locale, which you can infer based on their country code. Check out these customizations and more in the documentation.

Our tutorial assumed all verifications would occur via SMS but the API also supports Voice. You can add radio buttons on the StartVerificationViewController for your user to decide how they want to receive their verification. You can also add a nicer country code picker with one of the many libraries out there.

Image courtesy of https://github.com/4taras4/CountryCode

For details on how to do phone verification on your desktop site, check out one of our phone verification tutorials. Once you have verified the user’s identity, you’ll still want to securely authenticate them at login. For that you can use our Authy Two-factor Authentication API. If you have any questions or want to chat about security, please don’t hesitate to reach out to me on Twitter @kelleyrobinson.

Authors
Sign up and start building
Not ready yet? Talk to an expert.