Implementing two-factor authentication using Bandwidth’s Voice API

Notice: This post uses a legacy version of our API and two-factor authentication solution. Visit out developer portal to view current API documentation and our two-factor authentication page to learn more about our two-factor authentication solutions.
Two-factor authentication is a method of user authentication that ideally requires two out of a possible three categories of user authentication. These categories are:
- Something the user knows
- Something the user has
- Something the user is
A very simple method of two-factor authentication uses category 1 (something the user knows) and category 2 (something the user has) is set up through a username and password combination, and requires a phone to finalize the login process (via a text message or voice call).
Bandwidth’s voice API can be easily used to implement two-factor authentication through a user’s phone. During the user registration process, the system needs to generate some type of temporary secret code and link this code to the new user -ideally a 4 to 6 digit number. The user’s phone number would also need to be a required parameter for the registration process.
Once the registration process is complete, the system uses Bandwidth’s voice API to call the user. The system would then use the previously generated secret code to authenticate the user through the phone call. This authentication can be done by informing the user of the secret code through the phone call and having the user input the secret code on the login system, or by displaying the secret code on the login system and having the user input the digits via the phone’s keypad. Both of these methods can be implemented using Bandwidth’s voice API.
We’ve discussed two-factor authentication for user registration, but what about user login? During the user login process, simply use Bandwidth’s voice API to call the user on the user’s phone number saved in the login system, and prompt the user to press a digit on the keypad to authenticate the login process.
So we know we can use Bandwidth’s voice API to implement two-factor authentication through a user’s phone number, but why use two-factor authentication? Simply put, two-factor authentication improves the security of a system — it’s easier to guess a user’s password than to steal their phone number since humans are notoriously bad at choosing strong passwords. We like easy to remember passwords, but passwords that are easy for us to remember usually aren’t secure. Don’t believe me? Look at the data. At the end of 2017, Fortune published a list of the top 25 passwords for 2017. Easily rememberable passwords such as “123456” and “letmein” are on the top of this list.
You can use websites like https://howsecureismypassword.net/ to check the security, or better yet, lack of security, of the passwords in Fortune’s list (warning: it is not safe to input any of your passwords on any third party website).
Our end goal is to develop a system with the best security. Stronger password requirements are a good start, but two-factor authentication provides more security and is easier to enforce by the system. Below is sample code that shows how to develop a simple login system with two-factor authentication using Bandwidth’s voice API.
Take note that the focus of this sample code is to demonstrate usage of Bandwidth’s voice API. A more sophisticated login system would have more security features like password hashing. These were excluded in order to keep the focus on Bandwidth’s voice API. The full sample code with instructions on how to run it locally using Python and Flask can be found here.
Example of generating a random 4 digit authentication code for a Flask template
@app.route("/register", methods=['GET'])
def register_user_page():
code = "".join([str(random.randint(0,9)) for x in range(4)])
return render_template("authenticate.html", code=code, authenticate=url_for("register_user_authenticate"))
Example of a registration system that requires a user to authenticate a phone number
@app.route("/register/authenticate", methods=['POST'])
def register_user_authenticate():
username = request.form['username']
password = request.form['password']
phone = request.form['phone']
auth_code = request.form['auth_code']
call_id = VOICE_API.create_call(
from_ = BANDWIDTH_PHONE_NUMBER,
to = phone
)
wait_for_call_action(call_id)
if VOICE_API.get_call(call_id)['state'] != 'active':
return redirect(url_for("register_user_failure"), code=307) #code=307 is to preserve the previously used http method (post)
VOICE_API.play_audio_to_call(call_id, sentence="Please input your secret code")
gather_id = VOICE_API.create_call_gather(call_id, max_digits=4, inter_digit_timeout=10)
complete_gather(call_id, gather_id)
data = VOICE_API.get_call_gather(call_id, gather_id)
if data['reason'] != 'max-digits':
VOICE_API.hangup_call(call_id)
return redirect(url_for("register_user_failure"), code=307) #code=307 is to preserve the previously used http method (post)
digits = data['digits']
if digits == auth_code:
session['username'] = username
session['password'] = password
session['phone'] = phone
VOICE_API.play_audio_to_call(call_id, sentence="You have been authenticated")
time.sleep(3)
VOICE_API.hangup_call(call_id)
return redirect(url_for("register_user_success"), code=307) #code=307 is to preserve the previously used http method (post)
else:
VOICE_API.play_audio_to_call(call_id, sentence="Failure to authenticate")
time.sleep(3)
VOICE_API.hangup_call(call_id)
return redirect(url_for("register_user_failure"), code=307) #code=307 is to preserve the previously used http method (post)
Example of a login system that makes a phone call to a previously authenticated phone number
@app.route("/login", methods=['POST'])
def login_user():
username = request.form['username']
password = request.form['password']
if not (user_exists(username) and authenticate_username_password(username, password)):
return render_template("login_fail.html")
phone = get_username_phone(username)
call_id = VOICE_API.create_call(
from_ = BANDWIDTH_PHONE_NUMBER,
to = phone
)
wait_for_call_action(call_id)
VOICE_API.play_audio_to_call(call_id, sentence="Please press 1 to authenticate the login, or anything else to deny the login")
gather_id = VOICE_API.create_call_gather(call_id, max_digits=1, inter_digit_timeout=10)
complete_gather(call_id, gather_id)
data = VOICE_API.get_call_gather(call_id, gather_id)
if data['reason'] != 'max-digits':
VOICE_API.hangup_call(call_id)
return render_template("login_fail.html")
digits = data['digits']
if digits == "1":
VOICE_API.play_audio_to_call(call_id, sentence="You have authenticated the login")
time.sleep(3)
VOICE_API.hangup_call(call_id)
return render_template("login_success.html")
else:
VOICE_API.play_audio_to_call(call_id, sentence="You have denied the login")
time.sleep(3)
VOICE_API.hangup_call(call_id)
return render_template("login_fail.html")