In these times of heightened focus on privacy, security and password leaks, there is no reason to hesitate when an easy to implement solution is right in front of you.

A way to make your authentication process more secure is by adding in more factors. One such factor, which is widely used because of its convenience, is a one-time SMS code sent to the account owners phone number when, and only when, a valid password has been entered. And only by typing in that extra one-time code, the user is authenticated. This is known as a SMS password, SMS passcode, OneCode etc. and provided by Authy and similar services.

By using GatewayAPI and a couple of neat libraries, it should be straightforward to implement. So let’s give it a try.

Connecting to the API

Before we can start authenticating account owners by using a two-factor process consisting of their password and a one-time SMS code, we need to figure out how to send a SMS using GatewayAPI in the first place.

In this post, I will be using Python for the code examples. For Ruby and PHP developers, we have examples in our documentation for using GatewayAPI with those languages among others.

GatewayAPI has a couple of ways to authenticate. We will focus on using OAuth1 since it is both supported and straightforward.

Go to your dashboard and in the sidebar, choose settings -> OAuth keys. There should already exist a key-set, with a label saying it was created by the system. Click on the key-button on the right and you will be presented by your key and secret. These are what you will use to authenticate with the API.

Sending a SMS is now straightforward. Using Python it can be done as so:

Install dependencies with: pip install requests_oauthlib

from requests_oauthlib import OAuth1Session
key = '**********YOUR KEY**********'
secret = '**********YOUR SECRET**********'
gwapi = OAuth1Session(key, client_secret=secret)
req = {
    'recipients': [{'msisdn': 4512345678}],  # remember to change
    'message': 'First SMS sent using GatewayAPI',
}
res = gwapi.post('https://gatewayapi.com/rest/mtsms', json=req)
res.raise_for_status()

You can find examples on how to sent a SMS using more languages in our documentation.

Creating a two-factor auth (2FA) process

The two-factor process will include two steps. The first is authenticating with a username and password, and if that succeeds, the second step is authenticating with a one-time code that will be sent to you as a SMS using GatewayAPIs Rest API.

Since it is a two-factor process, we need a way to store some data between the two steps. For this we will use a Redis server, but it could be a session object, a MySQL database or any other way to store state between the two steps.

We also use PyOTP, which is a library that among other things makes it easy to make one-time passwords. For Ruby developers there is a library similar to PyOTP called ROTP. For PHP developers there is OTPHP.

We will create an AuthClient, which will do all the heavy lifting for us. In this article we will only focus on the process of two-factor authentication and sending the one time SMS codes, and not on how you authenticate your users username and passwords.

def step_one(self, username, password, phone):
    # code that authorizes username and password goes here
    # at this point, it should have been established that username and
    # password is legit and a user id should have been acquired
    user_id = 1

    # Generate a random secret for the user, recommended in a 2FA setup
    self.redis.setnx('secret:{}'.format(user_id), pyotp.random_base32(length=32))
    secret = self.redis.get('secret:{}'.format(user_id))

    # Counter that increases for each auth attempt for the particular user
    count = self.redis.incr('counter:{}'.format(user_id))

    hotp = pyotp.HOTP(secret, digits=6)
    sms_code = hotp.at(count)
    self.send_sms(sms_code, phone)
    return user_id

We use a counter in our Redis database that increments for each authentication attempt for the particular user. This count will be seeded to our PyOTP instance to ensure uniqueness. We then acquire a new code from PyOTP. We will use this to authenticate the SMS code in the second step. We then send off the SMS code to the users phone number using a function that is almost identical to our previous SMS-sending example. At last we return the ID of the user that matched the username and password.

We store a random secret for each user on demand in redis, but you can generate and store this secret in any way you like, just make sure it’s secure. More on management of secrets.

def step_two(self, auth_user_id, sms_code):
    secret = self.redis.get('secret:{}'.format(user_id))
    hotp = pyotp.HOTP(secret, digits=6)
    count = self.redis.get('counter:{}'.format(auth_user_id))
    if not hotp.verify(sms_code, count):
        raise Exception('Not a valid SMS code for user')

Step two is even simpler. We provide the obtained user_id that was returned from step_one and the SMS code that was sent to the users phone number. We look up the current counter in our Redis and make sure the SMS code matches.

Using all of this our flow in code could look something like this for step one:

try:
    ac = AuthClient()
    user_id = ac.step_one('username', 'password', 4521324354)
    print("SMS code sent")
except:
    print("Failed to sent SMS code")

And for step two:

try:
    ac = AuthClient()
    token = ac.step_two(user_id, sms_code)
    print("Authentication succeeded")
except Exception as e:
    print("Authentication failed")

If the authentication is a success, the user was authenticated using a two-factor process, and an authentication token was obtained.

The example code is focused on showcasing the technique of using GatewayAPI and how to use one-time passwords. In a production setting, modern security measures such as brute-force prevention and encryption of all sensitive data should be applied to your solution to fend off attackers. Take a look at RFC4226s consideration section for what you should be wary of when using PyOTP or similar libraries and techniques.

There are many possibilities of including SMS in your applications. Two-factor authentication consisting of a password and a SMS code is only one of them. With our easy and convenient APIs it is easy to make your authentication processes more secure without making it terribly complicated for all of your SMS-receiving users.

Code example

I have included the full client for the examples in Python as a reference, but in any case, customization will be needed for your solution.

Python AuthClient

import pyotp
import redis
from requests_oauthlib import OAuth1Session


class AuthClient(object):

    def __init__(self):
        self.redis = redis.StrictRedis(host='localhost', port=6379, db=0)

    def send_sms(self, sms_code, phone):
        key = '**********YOUR KEY**********'
        secret = '**********YOUR SECRET**********'
        gwapi = OAuth1Session(key, client_secret=secret)
        req = {
            'recipients': [{'msisdn': phone}],
            'message': 'Your code is {}'.format(sms_code),
            'sender': 'SMS Verify',
        }
        res = gwapi.post('https://gatewayapi.com/rest/mtsms', json=req)
        res.raise_for_status()

    def step_one(self, username, password, phone):
        # code that authorizes username and password goes here
        # at this point, it should have been established that username and
        # password is legit and a user id should have been acquired
        user_id = 1

        # Generate a random secret for the user, recommended in a 2FA setup
        self.redis.setnx('secret:{}'.format(user_id), pyotp.random_base32(length=32))
        secret = self.redis.get('secret:{}'.format(user_id))

        # Counter that increases for each auth attempt for the particular user
        count = self.redis.incr('counter:{}'.format(user_id))

        hotp = pyotp.HOTP(secret, digits=6)
        sms_code = hotp.at(count)
        self.send_sms(sms_code, phone)
        return user_id

    def step_two(self, auth_user_id, sms_code):
        secret = self.redis.get('secret:{}'.format(user_id))
        hotp = pyotp.HOTP(secret, digits=6)
        count = self.redis.get('counter:{}'.format(auth_user_id))
        if not hotp.verify(sms_code, count):
            raise Exception('Not a valid SMS code for user')