Account Verification with Authy, Python and Flask


(warning)

Warning

As of November 2022, Twilio no longer provides support for Authy SMS/Voice-only customers. Customers who were also using Authy TOTP or Push prior to March 1, 2023 are still supported. The Authy API is now closed to new customers and will be fully deprecated in the future.

For new development, we encourage you to use the Verify v2 API.

Existing customers will not be impacted at this time until Authy API has reached End of Life. For more information about migration, see Migrating from Authy to Verify for SMS(link takes you to an external page).

Ready to add Authy user account verification to your Flask application? Don't worry, this will be the easiest thing you do all day.

Here's how it all works at a high level:

  1. A user begins the registration process by entering their information (including a phone number) into a signup form.
  2. The authentication system sends a one-time password to the user's mobile phone to verify the phone number.
  3. The user enters that one-time password into a form to complete registration.
  4. The user sees a success page and receives an SMS alerting them that their account has been created!

To get this done, you'll be working with the following Twilio-powered APIs:

Authy REST API

  • Authy Docs: Find quickstarts, documentation, and info on the helper libraries.

Twilio REST API

  • Messages Resource: We will use Twilio directly to send our users a confirmation message after they creates their accounts.

Initialize Flask application

initialize-flask-application page anchor

account_verification_flask/__init__.py

1
from flask import Flask
2
from flask_login import LoginManager
3
from flask_sqlalchemy import SQLAlchemy
4
from flask_bcrypt import Bcrypt
5
from account_verification_flask.config import config_env_files
6
7
app = Flask(__name__)
8
db = SQLAlchemy()
9
bcrypt = Bcrypt()
10
login_manager = LoginManager()
11
12
13
def prepare_app(
14
environment='development', p_db=db, p_bcrypt=bcrypt, p_login_manager=login_manager
15
):
16
app.config.from_object(config_env_files[environment])
17
18
p_db.init_app(app)
19
p_bcrypt.init_app(app)
20
p_login_manager.init_app(app)
21
p_login_manager.login_view = 'register'
22
return app
23
24
25
prepare_app()
26
27
import account_verification_flask.views # noqa F402
28

All of this can be done in under a half an hour with the simplicity and power of Authy and Twilio. Let's get started!


Application configuration

application-configuration page anchor

For this application we'll be using the The Twilio Python Helper Library and the Python Client for Authy API(link takes you to an external page). We require some configuration from your side before we can begin.

Edit the DevelopmentConfig class constant values located in the account_verification_flask/config.py file:

1
AUTHY_KEY = 'your_authy_key'
2
3
TWILIO_ACCOUNT_SID = 'your_twilio_account_sid'
4
TWILIO_AUTH_TOKEN = 'your_twilio_auth_token'
5
TWILIO_NUMBER = 'your_twilio_phone_number'
6
7
SQLALCHEMY_DATABASE_URI = 'sqlite://'
8

Note that you have to replace the placeholders your_twilio_account_sid, your_twilio_auth_token, your_twilio_phone_number and your_authy_key with your information. You can find all of those parameters (and more) in your Twilio Account Console(link takes you to an external page) and your Authy dashboard(link takes you to an external page).

Configure your application with Twilio and Authy

configure-your-application-with-twilio-and-authy page anchor

account_verification_flask/config.py

1
import os
2
3
from dotenv import load_dotenv
4
5
load_dotenv()
6
7
basedir = os.path.abspath(os.path.dirname(__file__))
8
9
10
class DefaultConfig(object):
11
SECRET_KEY = os.environ.get('SECRET_KEY', 'secret-key')
12
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI')
13
SQLALCHEMY_TRACK_MODIFICATIONS = False
14
15
TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID', None)
16
TWILIO_API_KEY = os.environ.get('TWILIO_API_KEY', None)
17
TWILIO_API_SECRET = os.environ.get('TWILIO_API_SECRET', None)
18
TWILIO_NUMBER = os.environ.get('TWILIO_NUMBER', None)
19
AUTHY_API_KEY = os.environ.get('AUTHY_API_KEY', None)
20
21
DEBUG = False
22
23
24
class DevelopmentConfig(DefaultConfig):
25
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'dev.sqlite')
26
DEBUG = True
27
28
29
class TestConfig(DefaultConfig):
30
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
31
SQLALCHEMY_ECHO = True
32
DEBUG = True
33
TESTING = True
34
WTF_CSRF_ENABLED = False
35
36
37
config_env_files = {
38
'testing': 'account_verification_flask.config.TestConfig',
39
'development': 'account_verification_flask.config.DevelopmentConfig',
40
'production': 'account_verification_flask.config.Default',
41
}
42

Now that we've got the setup boilerplate out of the way, let's take a look at the User model.


We'll be using the Flask-Login(link takes you to an external page) library for our user session management. To integrate this library into our code we need to implement a few properties and methods(link takes you to an external page), then we're good to go.

Note the new variable authy_user_id, which is implemented for storing the user's Authy identification token.

User model with DB configuration and model methods for Flask-Login

user-model-with-db-configuration-and-model-methods-for-flask-login page anchor

account_verification_flask/models/models.py

1
# from flask.ext.login import UserMixin
2
from account_verification_flask import db, bcrypt
3
4
5
class User(db.Model):
6
__tablename__ = "users"
7
8
id = db.Column(db.Integer, primary_key=True)
9
name = db.Column(db.String, nullable=False)
10
email = db.Column(db.String, nullable=False)
11
password = db.Column(db.String)
12
phone_number = db.Column(db.String, nullable=False)
13
country_code = db.Column(db.String, nullable=False)
14
phone_number_confirmed = db.Column(db.Boolean, nullable=False, default=False)
15
authy_user_id = db.Column(db.String, nullable=True)
16
17
def __init__(self, name, email, password, phone_number, country_code):
18
self.name = name
19
self.email = email
20
self.password = bcrypt.generate_password_hash(password)
21
self.phone_number = phone_number
22
self.country_code = country_code
23
self.phone_number_confirmed = False
24
25
def is_authenticated(self):
26
return True
27
28
def is_active(self):
29
return True
30
31
def is_anonymous(self):
32
return False
33
34
def get_id(self):
35
return str(self.id)
36
37
def __str__(self):
38
return self.name
39
40
def __repr__(self):
41
return f'<User: {self.name}>'
42

Next we're going to visit the registration form on the client side.


In order to validate the user's account and register the user, we need a mobile number with a country code. We can then use Authy to send a verification code via SMS.

In this example we're validating and rendering the forms with the WTForms(link takes you to an external page) library. This allows us to define the forms as classes inside Python.

account_verification_flask/templates/register.html

1
{% extends "layout.html" %}
2
3
{% block content %}
4
5
<h1>We're going to be *BEST* friends</h1>
6
<p> Thanks for your interest in signing up! Can you tell us a bit about yourself?</p>
7
8
9
<form method="POST" class="form-horizontal" role="form">
10
{% from "_formhelpers.html" import render_errors, render_field %}
11
{{ form.csrf_token }}
12
{{ render_errors(form) }}
13
<hr/>
14
15
{{ render_field(form.name, placeholder='Anakin Skywalker') }}
16
{{ render_field(form.email, placeholder='darth@vader.com') }}
17
{{ render_field(form.password) }}
18
{{ render_field(form.country_code, id="authy-countries" ) }}
19
{{ render_field(form.phone_number , type='number') }}
20
21
<div class="form-group">
22
<div class="col-md-offset-2 col-md-10">
23
<input type="submit" class="btn btn-primary" value="Sign Up" />
24
</div>
25
</div>
26
</form>
27
28
{% endblock %}
29

That's it for the client side. Now let's look at what happens when the user submits the form.


Registration Server Side Implementation

registration-server-side-implementation page anchor

Next our controller stores the new user, registers them with Authy's API, and requests a new verification code.

The registration view route

the-registration-view-route page anchor

account_verification_flask/views.py

1
from flask import request, flash, g
2
from flask_login import login_user, logout_user, current_user
3
from account_verification_flask import app, db, login_manager
4
from account_verification_flask.forms.forms import (
5
RegisterForm,
6
ResendCodeForm,
7
VerifyCodeForm,
8
)
9
from account_verification_flask.models.models import User
10
from account_verification_flask.services.authy_services import AuthyServices
11
from account_verification_flask.services.twilio_services import TwilioServices
12
from account_verification_flask.utilities import User_Already_Confirmed
13
from account_verification_flask.utilities.view_helpers import view, redirect_to
14
import account_verification_flask.utilities
15
16
17
@app.route('/')
18
@app.route('/home')
19
def home():
20
return view('index')
21
22
23
@app.route('/register', methods=["GET", "POST"])
24
def register():
25
form = RegisterForm()
26
if request.method == 'POST':
27
if form.validate_on_submit():
28
29
if User.query.filter(User.email == form.email.data).count() > 0:
30
form.email.errors.append(
31
account_verification_flask.utilities.User_Email_Already_In_Use
32
)
33
return view('register', form)
34
35
user = User(
36
name=form.name.data,
37
email=form.email.data,
38
password=form.password.data,
39
country_code=form.country_code.data,
40
phone_number=form.phone_number.data,
41
)
42
db.session.add(user)
43
db.session.commit()
44
45
authy_services = AuthyServices()
46
if authy_services.request_phone_confirmation_code(user):
47
db.session.commit()
48
flash(account_verification_flask.utilities.Verification_Code_Sent)
49
return redirect_to('verify', email=form.email.data)
50
51
form.email.errors.append(
52
account_verification_flask.utilities.Verification_Code_Not_Sent
53
)
54
55
else:
56
return view('register', form)
57
58
return view('register', form)
59
60
61
@app.route('/verify', methods=["GET", "POST"])
62
@app.route('/verify/<email>', methods=["GET"])
63
def verify():
64
form = VerifyCodeForm()
65
if request.method == 'POST':
66
if form.validate_on_submit():
67
user = User.query.filter(User.email == form.email.data).first()
68
69
if user is None:
70
form.email.errors.append(
71
account_verification_flask.utilities.User_Not_Found_For_Given_Email
72
)
73
return view('verify_registration_code', form)
74
75
if user.phone_number_confirmed:
76
form.email.errors.append(User_Already_Confirmed)
77
return view('verify_registration_code', form)
78
79
authy_services = AuthyServices()
80
if authy_services.confirm_phone_number(user, form.verification_code.data):
81
user.phone_number_confirmed = True
82
db.session.commit()
83
login_user(user, remember=True)
84
twilio_services = TwilioServices()
85
twilio_services.send_registration_success_sms(
86
"+{0}{1}".format(user.country_code, user.phone_number)
87
)
88
return redirect_to('status')
89
else:
90
form.email.errors.append(
91
account_verification_flask.utilities.Verification_Unsuccessful
92
)
93
return view('verify_registration_code', form)
94
else:
95
form.email.data = request.args.get('email')
96
return view('verify_registration_code', form)
97
98
99
@app.route('/resend', methods=["GET", "POST"])
100
@app.route('/resend/<email>', methods=["GET"])
101
def resend(email=""):
102
form = ResendCodeForm()
103
104
if request.method == 'POST':
105
if form.validate_on_submit():
106
user = User.query.filter(User.email == form.email.data).first()
107
108
if user is None:
109
form.email.errors.append(
110
account_verification_flask.utilities.User_Not_Found_For_Given_Email
111
)
112
return view('resend_confirmation_code', form)
113
114
if user.phone_number_confirmed:
115
form.email.errors.append(
116
account_verification_flask.utilities.User_Already_Confirmed
117
)
118
return view('resend_confirmation_code', form)
119
authy_services = AuthyServices()
120
if authy_services.request_phone_confirmation_code(user):
121
flash(account_verification_flask.utilities.Verification_Code_Resent)
122
return redirect_to('verify', email=form.email.data)
123
else:
124
form.email.errors.append(
125
account_verification_flask.utilities.Verification_Code_Not_Sent
126
)
127
else:
128
form.email.data = email
129
130
return view('resend_confirmation_code', form)
131
132
133
@app.route('/status')
134
def status():
135
return view('status')
136
137
138
@app.route('/logout', methods=["POST"])
139
def logout():
140
logout_user()
141
return redirect_to('home')
142
143
144
# controller utils
145
@app.before_request
146
def before_request():
147
g.user = current_user
148
149
150
@login_manager.user_loader
151
def load_user(user_id):
152
try:
153
return User.query.get(user_id)
154
except Exception:
155
return None
156

Next we'll set up our application to complete our user verification.


Verifying a User in Python

verifying-a-user-in-python page anchor

On the server we first check that the email belongs to a user that we haven't yet verified.

The process then has two critical steps:

  1. Communicate with Authy's API to check if the given code is correct.
  2. Send a confirmation SMS to the user using Twilio's API.

After that (assuming a success!) we redirect the user to a success page.

Verify a user's code and send a success SMS

verify-a-users-code-and-send-a-success-sms page anchor

account_verification_flask/views.py

1
from flask import request, flash, g
2
from flask_login import login_user, logout_user, current_user
3
from account_verification_flask import app, db, login_manager
4
from account_verification_flask.forms.forms import (
5
RegisterForm,
6
ResendCodeForm,
7
VerifyCodeForm,
8
)
9
from account_verification_flask.models.models import User
10
from account_verification_flask.services.authy_services import AuthyServices
11
from account_verification_flask.services.twilio_services import TwilioServices
12
from account_verification_flask.utilities import User_Already_Confirmed
13
from account_verification_flask.utilities.view_helpers import view, redirect_to
14
import account_verification_flask.utilities
15
16
17
@app.route('/')
18
@app.route('/home')
19
def home():
20
return view('index')
21
22
23
@app.route('/register', methods=["GET", "POST"])
24
def register():
25
form = RegisterForm()
26
if request.method == 'POST':
27
if form.validate_on_submit():
28
29
if User.query.filter(User.email == form.email.data).count() > 0:
30
form.email.errors.append(
31
account_verification_flask.utilities.User_Email_Already_In_Use
32
)
33
return view('register', form)
34
35
user = User(
36
name=form.name.data,
37
email=form.email.data,
38
password=form.password.data,
39
country_code=form.country_code.data,
40
phone_number=form.phone_number.data,
41
)
42
db.session.add(user)
43
db.session.commit()
44
45
authy_services = AuthyServices()
46
if authy_services.request_phone_confirmation_code(user):
47
db.session.commit()
48
flash(account_verification_flask.utilities.Verification_Code_Sent)
49
return redirect_to('verify', email=form.email.data)
50
51
form.email.errors.append(
52
account_verification_flask.utilities.Verification_Code_Not_Sent
53
)
54
55
else:
56
return view('register', form)
57
58
return view('register', form)
59
60
61
@app.route('/verify', methods=["GET", "POST"])
62
@app.route('/verify/<email>', methods=["GET"])
63
def verify():
64
form = VerifyCodeForm()
65
if request.method == 'POST':
66
if form.validate_on_submit():
67
user = User.query.filter(User.email == form.email.data).first()
68
69
if user is None:
70
form.email.errors.append(
71
account_verification_flask.utilities.User_Not_Found_For_Given_Email
72
)
73
return view('verify_registration_code', form)
74
75
if user.phone_number_confirmed:
76
form.email.errors.append(User_Already_Confirmed)
77
return view('verify_registration_code', form)
78
79
authy_services = AuthyServices()
80
if authy_services.confirm_phone_number(user, form.verification_code.data):
81
user.phone_number_confirmed = True
82
db.session.commit()
83
login_user(user, remember=True)
84
twilio_services = TwilioServices()
85
twilio_services.send_registration_success_sms(
86
"+{0}{1}".format(user.country_code, user.phone_number)
87
)
88
return redirect_to('status')
89
else:
90
form.email.errors.append(
91
account_verification_flask.utilities.Verification_Unsuccessful
92
)
93
return view('verify_registration_code', form)
94
else:
95
form.email.data = request.args.get('email')
96
return view('verify_registration_code', form)
97
98
99
@app.route('/resend', methods=["GET", "POST"])
100
@app.route('/resend/<email>', methods=["GET"])
101
def resend(email=""):
102
form = ResendCodeForm()
103
104
if request.method == 'POST':
105
if form.validate_on_submit():
106
user = User.query.filter(User.email == form.email.data).first()
107
108
if user is None:
109
form.email.errors.append(
110
account_verification_flask.utilities.User_Not_Found_For_Given_Email
111
)
112
return view('resend_confirmation_code', form)
113
114
if user.phone_number_confirmed:
115
form.email.errors.append(
116
account_verification_flask.utilities.User_Already_Confirmed
117
)
118
return view('resend_confirmation_code', form)
119
authy_services = AuthyServices()
120
if authy_services.request_phone_confirmation_code(user):
121
flash(account_verification_flask.utilities.Verification_Code_Resent)
122
return redirect_to('verify', email=form.email.data)
123
else:
124
form.email.errors.append(
125
account_verification_flask.utilities.Verification_Code_Not_Sent
126
)
127
else:
128
form.email.data = email
129
130
return view('resend_confirmation_code', form)
131
132
133
@app.route('/status')
134
def status():
135
return view('status')
136
137
138
@app.route('/logout', methods=["POST"])
139
def logout():
140
logout_user()
141
return redirect_to('home')
142
143
144
# controller utils
145
@app.before_request
146
def before_request():
147
g.user = current_user
148
149
150
@login_manager.user_loader
151
def load_user(user_id):
152
try:
153
return User.query.get(user_id)
154
except Exception:
155
return None
156

What happens if the message was never sent, didn't arrive, or can't be found? Let's look at how to handle those scenarios next.


Re-sending a Verification Code

re-sending-a-verification-code page anchor

The form for re-sending the code is a single line, so let's skip that detail for this tutorial. Instead, let's just take a look at the controller function for resending verifications.

This controller loads the User associated with the request and then uses the same Authy API method we used earlier to resend the code. Pretty straightforward, right?

Re-send a verification code

re-send-a-verification-code page anchor

account_verification_flask/views.py

1
from flask import request, flash, g
2
from flask_login import login_user, logout_user, current_user
3
from account_verification_flask import app, db, login_manager
4
from account_verification_flask.forms.forms import (
5
RegisterForm,
6
ResendCodeForm,
7
VerifyCodeForm,
8
)
9
from account_verification_flask.models.models import User
10
from account_verification_flask.services.authy_services import AuthyServices
11
from account_verification_flask.services.twilio_services import TwilioServices
12
from account_verification_flask.utilities import User_Already_Confirmed
13
from account_verification_flask.utilities.view_helpers import view, redirect_to
14
import account_verification_flask.utilities
15
16
17
@app.route('/')
18
@app.route('/home')
19
def home():
20
return view('index')
21
22
23
@app.route('/register', methods=["GET", "POST"])
24
def register():
25
form = RegisterForm()
26
if request.method == 'POST':
27
if form.validate_on_submit():
28
29
if User.query.filter(User.email == form.email.data).count() > 0:
30
form.email.errors.append(
31
account_verification_flask.utilities.User_Email_Already_In_Use
32
)
33
return view('register', form)
34
35
user = User(
36
name=form.name.data,
37
email=form.email.data,
38
password=form.password.data,
39
country_code=form.country_code.data,
40
phone_number=form.phone_number.data,
41
)
42
db.session.add(user)
43
db.session.commit()
44
45
authy_services = AuthyServices()
46
if authy_services.request_phone_confirmation_code(user):
47
db.session.commit()
48
flash(account_verification_flask.utilities.Verification_Code_Sent)
49
return redirect_to('verify', email=form.email.data)
50
51
form.email.errors.append(
52
account_verification_flask.utilities.Verification_Code_Not_Sent
53
)
54
55
else:
56
return view('register', form)
57
58
return view('register', form)
59
60
61
@app.route('/verify', methods=["GET", "POST"])
62
@app.route('/verify/<email>', methods=["GET"])
63
def verify():
64
form = VerifyCodeForm()
65
if request.method == 'POST':
66
if form.validate_on_submit():
67
user = User.query.filter(User.email == form.email.data).first()
68
69
if user is None:
70
form.email.errors.append(
71
account_verification_flask.utilities.User_Not_Found_For_Given_Email
72
)
73
return view('verify_registration_code', form)
74
75
if user.phone_number_confirmed:
76
form.email.errors.append(User_Already_Confirmed)
77
return view('verify_registration_code', form)
78
79
authy_services = AuthyServices()
80
if authy_services.confirm_phone_number(user, form.verification_code.data):
81
user.phone_number_confirmed = True
82
db.session.commit()
83
login_user(user, remember=True)
84
twilio_services = TwilioServices()
85
twilio_services.send_registration_success_sms(
86
"+{0}{1}".format(user.country_code, user.phone_number)
87
)
88
return redirect_to('status')
89
else:
90
form.email.errors.append(
91
account_verification_flask.utilities.Verification_Unsuccessful
92
)
93
return view('verify_registration_code', form)
94
else:
95
form.email.data = request.args.get('email')
96
return view('verify_registration_code', form)
97
98
99
@app.route('/resend', methods=["GET", "POST"])
100
@app.route('/resend/<email>', methods=["GET"])
101
def resend(email=""):
102
form = ResendCodeForm()
103
104
if request.method == 'POST':
105
if form.validate_on_submit():
106
user = User.query.filter(User.email == form.email.data).first()
107
108
if user is None:
109
form.email.errors.append(
110
account_verification_flask.utilities.User_Not_Found_For_Given_Email
111
)
112
return view('resend_confirmation_code', form)
113
114
if user.phone_number_confirmed:
115
form.email.errors.append(
116
account_verification_flask.utilities.User_Already_Confirmed
117
)
118
return view('resend_confirmation_code', form)
119
authy_services = AuthyServices()
120
if authy_services.request_phone_confirmation_code(user):
121
flash(account_verification_flask.utilities.Verification_Code_Resent)
122
return redirect_to('verify', email=form.email.data)
123
else:
124
form.email.errors.append(
125
account_verification_flask.utilities.Verification_Code_Not_Sent
126
)
127
else:
128
form.email.data = email
129
130
return view('resend_confirmation_code', form)
131
132
133
@app.route('/status')
134
def status():
135
return view('status')
136
137
138
@app.route('/logout', methods=["POST"])
139
def logout():
140
logout_user()
141
return redirect_to('home')
142
143
144
# controller utils
145
@app.before_request
146
def before_request():
147
g.user = current_user
148
149
150
@login_manager.user_loader
151
def load_user(user_id):
152
try:
153
return User.query.get(user_id)
154
except Exception:
155
return None
156

Let's take a step back and see how we can use Authy to resend a verification code to an unverified user.


Sending a Token on Account Creation

sending-a-token-on-account-creation page anchor

In order to end up with a cleaner and decoupled design we'll encapsulate all of Authy's related features in an AuthyService. This class will hold a shared class instance of the AuthyApiClient class.

Once the user has an authyId we can send a verification code to that user's mobile phone.

Send Authy token via SMS

send-authy-token-via-sms page anchor

account_verification_flask/services/authy_services.py

1
import account_verification_flask.utilities
2
from account_verification_flask.utilities.settings import AuthySettings
3
from authy.api import AuthyApiClient
4
5
6
class AuthyServices:
7
authy_client = None
8
9
def __init__(self):
10
if AuthyServices.authy_client is None:
11
AuthyServices.authy_client = AuthyApiClient(AuthySettings.key())
12
13
def request_phone_confirmation_code(self, user):
14
if user is None:
15
raise ValueError(account_verification_flask.utilities.User_Id_Not_Found)
16
17
if user.authy_user_id is None:
18
self._register_user_under_authy(user)
19
20
sms = self.authy_client.users.request_sms(user.authy_user_id, {'force': True})
21
return not sms.ignored()
22
23
def confirm_phone_number(self, user, verification_code):
24
if user is None:
25
raise ValueError(account_verification_flask.utilities.User_Id_Not_Found)
26
27
verification = self.authy_client.tokens.verify(
28
user.authy_user_id, verification_code
29
)
30
return verification.ok()
31
32
def _register_user_under_authy(self, user):
33
authy_user = self.authy_client.users.create(
34
user.email, user.phone_number, user.country_code
35
)
36
if authy_user.ok:
37
user.authy_user_id = authy_user.id
38

When our user is created successfully via the form we have implemented, we send a token to the user's mobile phone asking them to verify their account in our controller. When the code is sent, we redirect our users to another page where they can enter the received token, completing the verification process.


Authy provides us with a tokens.verify method that allows us to pass a user id and token. In this case we just need to check that the API request was successful and, if so, set a verified flag on the user.

Verify a user's token with AuthyServices

verify-a-users-token-with-authyservices page anchor

account_verification_flask/services/authy_services.py

1
import account_verification_flask.utilities
2
from account_verification_flask.utilities.settings import AuthySettings
3
from authy.api import AuthyApiClient
4
5
6
class AuthyServices:
7
authy_client = None
8
9
def __init__(self):
10
if AuthyServices.authy_client is None:
11
AuthyServices.authy_client = AuthyApiClient(AuthySettings.key())
12
13
def request_phone_confirmation_code(self, user):
14
if user is None:
15
raise ValueError(account_verification_flask.utilities.User_Id_Not_Found)
16
17
if user.authy_user_id is None:
18
self._register_user_under_authy(user)
19
20
sms = self.authy_client.users.request_sms(user.authy_user_id, {'force': True})
21
return not sms.ignored()
22
23
def confirm_phone_number(self, user, verification_code):
24
if user is None:
25
raise ValueError(account_verification_flask.utilities.User_Id_Not_Found)
26
27
verification = self.authy_client.tokens.verify(
28
user.authy_user_id, verification_code
29
)
30
return verification.ok()
31
32
def _register_user_under_authy(self, user):
33
authy_user = self.authy_client.users.create(
34
user.email, user.phone_number, user.country_code
35
)
36
if authy_user.ok:
37
user.authy_user_id = authy_user.id
38

That's it for token verification! Let's provide a nice user onboarding experience, and send a confirmation message to our new user.


Sending the Confirmation Message

sending-the-confirmation-message page anchor

Just as we did for our Authy client, we create a single instance of the Twilio REST API helper. It will be called twilio_client in this example.

After that, it's straightforward - send an SMS using the Twilio Python helper library to the same number we used in messages.create().

Send a registration success SMS from the TwilioServices class

send-a-registration-success-sms-from-the-twilioservices-class page anchor

account_verification_flask/services/twilio_services.py

1
import account_verification_flask.utilities
2
from account_verification_flask.utilities.settings import TwilioSettings
3
from twilio.rest import Client
4
5
6
class TwilioServices:
7
twilio_client = None
8
9
def __init__(self):
10
if TwilioServices.twilio_client is None:
11
TwilioServices.twilio_client = Client(
12
TwilioSettings.api_key(),
13
TwilioSettings.api_secret(),
14
TwilioSettings.account_sid(),
15
)
16
17
def send_registration_success_sms(self, to_number):
18
self.twilio_client.messages.create(
19
body=account_verification_flask.utilities.Signup_Complete,
20
to=to_number,
21
from_=TwilioSettings.phone_number(),
22
)
23

Congratulations! You've successfully verified new user accounts with Authy. Where can we take it from here?


In one tutorial, we've implemented account verification with Authy and Twilio, allowing your users to confirm accounts with their phone number! Now it's on you - let us know what you build on Twitter(link takes you to an external page), and check out these other tutorials:

Appointment Reminders

Use Twilio to automate the process of reaching out to your customers in advance of an upcoming appointment.

Two-Factor Authentication with Authy

Use Twilio and Twilio-powered Authy OneTouch to implement two-factor authentication (2FA) in your web app.